diff --git a/app/code/community/Cloudinary/Cloudinary/Model/Observer/Autoload.php b/app/code/community/Cloudinary/Cloudinary/Model/Observer/Autoload.php new file mode 100644 index 0000000..a45e253 --- /dev/null +++ b/app/code/community/Cloudinary/Cloudinary/Model/Observer/Autoload.php @@ -0,0 +1,12 @@ + - 3.0.3 + 3.1.0 @@ -92,6 +92,15 @@ + + + + singleton + cloudinary_cloudinary/observer_autoload + autoloadRegister + + + diff --git a/lib/Cloudinary/Api.php b/lib/Cloudinary/Api.php deleted file mode 100644 index cb1087c..0000000 --- a/lib/Cloudinary/Api.php +++ /dev/null @@ -1,449 +0,0 @@ -rate_limit_reset_at = strtotime($response->headers["X-FeatureRateLimit-Reset"]); - $this->rate_limit_allowed = intval($response->headers["X-FeatureRateLimit-Limit"]); - $this->rate_limit_remaining = intval($response->headers["X-FeatureRateLimit-Remaining"]); - } - } -} - - -namespace Cloudinary { - - -class Api { - static $CLOUDINARY_API_ERROR_CLASSES = array( - 400 => "\Cloudinary\Api\BadRequest", - 401 => "\Cloudinary\Api\AuthorizationRequired", - 403 => "\Cloudinary\Api\NotAllowed", - 404 => "\Cloudinary\Api\NotFound", - 409 => "\Cloudinary\Api\AlreadyExists", - 420 => "\Cloudinary\Api\RateLimited", - 500 => "\Cloudinary\Api\GeneralError" - ); - - function ping($options=array()) { - return $this->call_api("get", array("ping"), array(), $options); - } - - function usage($options=array()) { - return $this->call_api("get", array("usage"), array(), $options); - } - - function resource_types($options=array()) { - return $this->call_api("get", array("resources"), array(), $options); - } - - function resources($options=array()) { - $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); - $type = \Cloudinary::option_get($options, "type"); - $uri = array("resources", $resource_type); - if ($type) array_push($uri, $type); - return $this->call_api("get", $uri, $this->only($options, array("next_cursor", "max_results", "prefix", "tags", "context", "moderations", "direction", "start_at")), $options); - } - - function resources_by_tag($tag, $options=array()) { - $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); - $uri = array("resources", $resource_type, "tags", $tag); - return $this->call_api("get", $uri, $this->only($options, array("next_cursor", "max_results", "tags", "context", "moderations", "direction")), $options); - } - - function resources_by_moderation($kind, $status, $options=array()) { - $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); - $uri = array("resources", $resource_type, "moderations", $kind, $status); - return $this->call_api("get", $uri, $this->only($options, array("next_cursor", "max_results", "tags", "context", "moderations", "direction")), $options); - } - - function resources_by_ids($public_ids, $options=array()) { - $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); - $type = \Cloudinary::option_get($options, "type", "upload"); - $uri = array("resources", $resource_type, $type); - $params = array_merge($options, array("public_ids" => $public_ids)); - return $this->call_api("get", $uri, $this->only($params, array("public_ids", "tags", "moderations", "context")), $options); - } - - function resource($public_id, $options=array()) { - $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); - $type = \Cloudinary::option_get($options, "type", "upload"); - $uri = array("resources", $resource_type, $type, $public_id); - return $this->call_api("get", $uri, $this->only($options, array("exif", "colors", "faces", "image_metadata", "phash", "pages", "coordinates", "max_results")), $options); - } - - function restore($public_ids, $options=array()) { - $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); - $type = \Cloudinary::option_get($options, "type", "upload"); - $uri = array("resources", $resource_type, $type, "restore"); - $params = array_merge($options, array("public_ids" => $public_ids)); - return $this->call_api("post", $uri, $params, $options); - } - - function update($public_id, $options=array()) { - $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); - $type = \Cloudinary::option_get($options, "type", "upload"); - $uri = array("resources", $resource_type, $type, $public_id); - - $tags = \Cloudinary::option_get($options, "tags"); - $context = \Cloudinary::option_get($options, "context"); - $face_coordinates = \Cloudinary::option_get($options, "face_coordinates"); - $custom_coordinates = \Cloudinary::option_get($options, "custom_coordinates"); - $update_options = array_merge( - $this->only($options, array("moderation_status", "raw_convert", "ocr", "categorization", "detection", "similarity_search", "auto_tagging", "background_removal")), - array( - "tags" => $tags ? implode(",", \Cloudinary::build_array($tags)) : $tags, - "context" => $context ? \Cloudinary::encode_assoc_array($context) : $context, - "face_coordinates" => $face_coordinates ? \Cloudinary::encode_double_array($face_coordinates) : $face_coordinates, - "custom_coordinates" => $custom_coordinates ? \Cloudinary::encode_double_array($custom_coordinates) : $custom_coordinates, - ) - ); - - return $this->call_api("post", $uri, $update_options, $options); - } - - function delete_resources($public_ids, $options=array()) { - $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); - $type = \Cloudinary::option_get($options, "type", "upload"); - $uri = array("resources", $resource_type, $type); - return $this->call_api("delete", $uri, array_merge(array("public_ids"=>$public_ids), $this->only($options, array("keep_original", "invalidate", "transformation"))), $options); - } - - function delete_resources_by_prefix($prefix, $options=array()) { - $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); - $type = \Cloudinary::option_get($options, "type", "upload"); - $uri = array("resources", $resource_type, $type); - return $this->call_api("delete", $uri, array_merge(array("prefix"=>$prefix), $this->only($options, array("keep_original", "next_cursor", "invalidate"))), $options); - } - - function delete_all_resources($options=array()) { - $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); - $type = \Cloudinary::option_get($options, "type", "upload"); - $uri = array("resources", $resource_type, $type); - return $this->call_api("delete", $uri, array_merge(array("all"=>True), $this->only($options, array("keep_original", "next_cursor", "invalidate"))), $options); - } - - function delete_resources_by_tag($tag, $options=array()) { - $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); - $uri = array("resources", $resource_type, "tags", $tag); - return $this->call_api("delete", $uri, $this->only($options, array("keep_original", "next_cursor", "invalidate")), $options); - } - - function delete_derived_resources($derived_resource_ids, $options=array()) { - $uri = array("derived_resources"); - return $this->call_api("delete", $uri, array("derived_resource_ids"=>$derived_resource_ids), $options); - } - - function tags($options=array()) { - $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); - $uri = array("tags", $resource_type); - return $this->call_api("get", $uri, $this->only($options, array("next_cursor", "max_results", "prefix")), $options); - } - - function transformations($options=array()) { - return $this->call_api("get", array("transformations"), $this->only($options, array("next_cursor", "max_results")), $options); - } - - function transformation($transformation, $options=array()) { - $uri = array("transformations", $this->transformation_string($transformation)); - return $this->call_api("get", $uri, $this->only($options, array("next_cursor", "max_results")), $options); - } - - function delete_transformation($transformation, $options=array()) { - $uri = array("transformations", $this->transformation_string($transformation)); - $params = array(); - if (isset($options["invalidate"])) { - $params["invalidate"] = $options["invalidate"]; - } - return $this->call_api("delete", $uri, $params, $options); - } - - # updates - currently only supported update is the "allowed_for_strict" boolean flag - function update_transformation($transformation, $updates=array(), $options=array()) { - $uri = array("transformations", $this->transformation_string($transformation)); - $params = $this->only($updates, array("allowed_for_strict")); - if (isset($updates["unsafe_update"])) { - $params["unsafe_update"] = $this->transformation_string($updates["unsafe_update"]); - } - return $this->call_api("put", $uri, $params, $options); - } - - function create_transformation($name, $definition, $options=array()) { - $uri = array("transformations", $name); - return $this->call_api("post", $uri, array("transformation"=>$this->transformation_string($definition)), $options); - } - - function upload_presets($options=array()) { - return $this->call_api("get", array("upload_presets"), $this->only($options, array("next_cursor", "max_results")), $options); - } - - function upload_preset($name, $options=array()) { - $uri = array("upload_presets", $name); - return $this->call_api("get", $uri, $this->only($options, array("max_results")), $options); - } - - function delete_upload_preset($name, $options=array()) { - $uri = array("upload_presets", $name); - return $this->call_api("delete", $uri, array(), $options); - } - - function update_upload_preset($name, $options=array()) { - $uri = array("upload_presets", $name); - $params = \Cloudinary\Uploader::build_upload_params($options); - return $this->call_api("put", $uri, array_merge($params, $this->only($options, array("unsigned", "disallow_public_id"))), $options); - } - - function create_upload_preset($options=array()) { - $params = \Cloudinary\Uploader::build_upload_params($options); - return $this->call_api("post", array("upload_presets"), array_merge($params, $this->only($options, array("name", "unsigned", "disallow_public_id"))), $options); - } - - function root_folders($options=array()) { - return $this->call_api("get", array("folders"), array(), $options); - } - - function subfolders($of_folder_path, $options=array()) { - return $this->call_api("get", array("folders", $of_folder_path), array(), $options); - } - - function upload_mappings($options=array()) { - return $this->call_api("get", array("upload_mappings"), $this->only($options, array("next_cursor", "max_results")), $options); - } - - function upload_mapping($name, $options=array()) { - $uri = array("upload_mappings"); - $params = array("folder"=>$name); - return $this->call_api("get", $uri, $params, $options); - } - - function delete_upload_mapping($name, $options=array()) { - $uri = array("upload_mappings"); - $params = array("folder"=>$name); - return $this->call_api("delete", $uri, $params, $options); - } - - function update_upload_mapping($name, $options=array()) { - $uri = array("upload_mappings"); - $params = array("folder"=>$name); - return $this->call_api("put", $uri, array_merge($params, $this->only($options, array("template"))), $options); - } - - function create_upload_mapping($name, $options=array()) { - $uri = array("upload_mappings"); - $params = array("folder"=>$name); - return $this->call_api("post", $uri, array_merge($params, $this->only($options, array("template"))), $options); - } - - /** - * List all streaming profiles associated with the current customer - * @param array $options options - * @return Api\Response An array with a "data" key for results - */ - function list_streaming_profiles($options=array()) { - return $this->call_api("get", array("streaming_profiles"), array(), $options); - } - - /** - * Get the information of a single streaming profile - * @param $name the name of the profile - * @param array $options other options - * @return Api\Response An array with a "data" key for results - */ - function get_streaming_profile($name, $options=array()) { - $uri = array("streaming_profiles/" . $name); - return $this->call_api("get", $uri, array(), $options); - } - - /** - * Delete a streaming profile information. Predefined profiles are restored to the default setting. - * @param $name the name of the streaming profile to delete - * @param array $options additional options - * @return Api\Response - */ - function delete_streaming_profile($name, $options=array()) { - $uri = array("streaming_profiles/" . $name); - return $this->call_api("delete", $uri, array(), $options); - } - - /** - * Update an existing streaming profile - * @param $name the name of the prodile - * @param array $options additional options - * @return Api\Response - */ - function update_streaming_profile($name, $options=array()) { - $uri = array("streaming_profiles/" . $name); - $params = $this->prepare_streaming_profile_params($options); - return $this->call_api("put", $uri, $params, $options); - } - - /** - * Create a new streaming profile - * @param $name the name of the new profile. if the name is of a predefined profile, the profile will be modified. - * @param array $options additional options - * @return Api\Response - */ - function create_streaming_profile($name, $options = array()) { - $uri = array("streaming_profiles"); - $params = $this->prepare_streaming_profile_params($options); - $params["name"] = $name; - return $this->call_api("post", $uri, $params, $options); - } - - function call_api($method, $uri, $params, &$options) { - $prefix = \Cloudinary::option_get($options, "upload_prefix", \Cloudinary::config_get("upload_prefix", "https://api.cloudinary.com")); - $cloud_name = \Cloudinary::option_get($options, "cloud_name", \Cloudinary::config_get("cloud_name")); - if (!$cloud_name) throw new \InvalidArgumentException("Must supply cloud_name"); - $api_key = \Cloudinary::option_get($options, "api_key", \Cloudinary::config_get("api_key")); - if (!$api_key) throw new \InvalidArgumentException("Must supply api_key"); - $api_secret = \Cloudinary::option_get($options, "api_secret", \Cloudinary::config_get("api_secret")); - if (!$api_secret) throw new \InvalidArgumentException("Must supply api_secret"); - $api_url = implode("/", array_merge(array($prefix, "v1_1", $cloud_name), $uri)); - $params = array_filter($params,function($v){ return !is_null($v) && ($v !== "" );}); - if ($method == "get") - { - $api_url .= "?" . preg_replace("/%5B\d+%5D/", "%5B%5D", http_build_query($params)); - - } - - $ch = curl_init($api_url); - - if ($method != "get") - { - $post_params = array(); - if (array_key_exists("content_type", $options) && $options["content_type"] == 'application/json') - { - $headers = array( - "Content-type: application/json", - "Accept: application/json", - ); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - $post_params = json_encode($params); - } else { - foreach ($params as $key => $value) { - if (is_array($value)) { - $i = 0; - foreach ($value as $item) { - $post_params[$key . "[$i]"] = $item; - $i++; - } - } else { - $post_params[$key] = $value; - } - } - } - curl_setopt($ch, CURLOPT_POSTFIELDS, $post_params); - } - curl_setopt($ch, CURLOPT_HEADER, 1); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($method)); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_TIMEOUT, 60); - curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); - curl_setopt($ch, CURLOPT_USERPWD, "{$api_key}:{$api_secret}"); - curl_setopt($ch, CURLOPT_CAINFO,realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR."cacert.pem"); - curl_setopt($ch, CURLOPT_USERAGENT, \Cloudinary::userAgent()); - curl_setopt($ch, CURLOPT_PROXY, \Cloudinary::option_get($options, "api_proxy", \Cloudinary::config_get("api_proxy"))); - $response = $this->execute($ch); - $curl_error = NULL; - if(curl_errno($ch)) - { - $curl_error = curl_error($ch); - } - curl_close($ch); - if ($curl_error != NULL) { - throw new \Cloudinary\Api\GeneralError("Error in sending request to server - " . $curl_error); - } - if ($response->responseCode == 200) { - return new \Cloudinary\Api\Response($response); - } else { - $exception_class = \Cloudinary::option_get(self::$CLOUDINARY_API_ERROR_CLASSES, $response->responseCode); - if (!$exception_class) throw new \Cloudinary\Api\GeneralError("Server returned unexpected status code - {$response->responseCode} - {$response->body}"); - $json = $this->parse_json_response($response); - throw new $exception_class($json["error"]["message"]); - } - } - - # Based on http://snipplr.com/view/17242/ - protected function execute($ch) { - $string = curl_exec($ch); - $headers = array(); - $content = ''; - $str = strtok($string, "\n"); - $h = null; - while ($str !== false) { - if ($h and trim($str) === '') { - $h = false; - continue; - } - if ($h !== false and false !== strpos($str, ':')) { - $h = true; - list($headername, $headervalue) = explode(':', trim($str), 2); - $headervalue = ltrim($headervalue); - if (isset($headers[$headername])) - $headers[$headername] .= ',' . $headervalue; - else - $headers[$headername] = $headervalue; - } - if ($h === false) { - $content .= $str."\n"; - } - $str = strtok("\n"); - } - $result = new \stdClass; - $result->headers = $headers; - $result->body = trim($content); - $result->responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - return $result; - } - - static function parse_json_response($response) { - $result = json_decode($response->body, TRUE); - if ($result == NULL) { - $error = json_last_error(); - throw new \Cloudinary\Api\GeneralError("Error parsing server response ({$response->responseCode}) - {$response->body}. Got - {$error}"); - } - return $result; - } - - protected function only(&$hash, $keys) { - $result = array(); - foreach ($keys as $key) { - if (isset($hash[$key])) $result[$key] = $hash[$key]; - } - - return $result; - } - - protected function transformation_string($transformation) { - return is_string($transformation) ? $transformation : \Cloudinary::generate_transformation_string($transformation); - } - - /** - * Prepare streaming profile parameters for API calls - * @param $options the options passed to the API - * @return array A single profile parameters - */ - protected function prepare_streaming_profile_params($options) { - $params = $this->only($options, array("display_name")); - if (isset($options['representations'])) { - $array_map = array_map( - function ($representation) { - return array("transformation" => \Cloudinary::generate_transformation_string($representation)); - }, $options['representations']); - $params["representations"] = json_encode($array_map); - } - return $params; - } -} - -} diff --git a/lib/Cloudinary/AuthToken.php b/lib/Cloudinary/AuthToken.php deleted file mode 100644 index 1412302..0000000 --- a/lib/Cloudinary/AuthToken.php +++ /dev/null @@ -1,72 +0,0 @@ -(\d+\.)?\d+)(?P[%pP])?$/'; - const RANGE_RE = '/^(\d+\.)?\d+[%pP]?\.\.(\d+\.)?\d+[%pP]?$/'; - - const VERSION = "1.8.0"; - /** @internal Do not change this value */ - const USER_AGENT = "CloudinaryPHP/1.8.0"; - - /** - * Additional information to be passed with the USER_AGENT, e.g. "CloudinaryMagento/1.0.1". This value is set in platform-specific - * implementations that use cloudinary_php. - * - * The format of the value should be /Version[ (comment)]. - * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 - * - * Do not set this value in application code! - * - * @var string - */ - public static $USER_PLATFORM = ""; - - public static $DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION = array("width"=>"auto", "crop"=>"limit"); - - private static $config = NULL; - public static $JS_CONFIG_PARAMS = array("api_key", "cloud_name", "private_cdn", "secure_distribution", "cdn_subdomain"); - - /** - * Provides the USER_AGENT string that is passed to the Cloudinary servers. - * - * Prepends {@link $USER_PLATFORM} if it is defined. - * - * @return string - */ - public static function userAgent() - { - if (self::$USER_PLATFORM == "") { - return self::USER_AGENT; - } else { - return self::$USER_PLATFORM . " " . self::USER_AGENT; - } - } - - public static function is_not_null ($var) { return !is_null($var);} - - public static function config($values = NULL) { - if (self::$config == NULL) { - self::reset_config(); - } - if ($values != NULL) { - self::$config = array_merge(self::$config, $values); - } - return self::$config; - } - - public static function reset_config() { - self::config_from_url(getenv("CLOUDINARY_URL")); - } - - public static function config_from_url($cloudinary_url) { - self::$config = array(); - if ($cloudinary_url) { - $uri = parse_url($cloudinary_url); - $q_params = array(); - if (isset($uri["query"])) { - parse_str($uri["query"], $q_params); - } - $private_cdn = isset($uri["path"]) && $uri["path"] != "/"; - $config = array_merge($q_params, array( - "cloud_name" => $uri["host"], - "api_key" => $uri["user"], - "api_secret" => $uri["pass"], - "private_cdn" => $private_cdn)); - if ($private_cdn) { - $config["secure_distribution"] = substr($uri["path"], 1); - } - self::$config = array_merge(self::$config, $config); - } - } - - public static function config_get($option, $default=NULL) { - return Cloudinary::option_get(self::config(), $option, $default); - } - - public static function option_get($options, $option, $default=NULL) { - if (isset($options[$option])) { - return $options[$option]; - } else { - return $default; - } - } - - public static function option_consume(&$options, $option, $default=NULL) { - if (isset($options[$option])) { - $value = $options[$option]; - unset($options[$option]); - return $value; - } else { - unset($options[$option]); - return $default; - } - } - - public static function build_array($value) { - if (is_array($value) && !Cloudinary::is_assoc($value)) { - return $value; - } else if ($value === NULL) { - return array(); - } else { - return array($value); - } - } - - public static function encode_array($array) { - return implode(",", Cloudinary::build_array($array)); - } - - public static function encode_double_array($array) { - $array = Cloudinary::build_array($array); - if (count($array) > 0 && !is_array($array[0])) { - return Cloudinary::encode_array($array); - } else { - $array = array_map('Cloudinary::encode_array', $array); - } - - return implode("|", $array); - } - - public static function encode_assoc_array($array) { - if (Cloudinary::is_assoc($array)){ - $encoded = array(); - foreach ($array as $key => $value) { - $value = !empty($value) - ? preg_replace('/([\|=])/', '\\\$1', $value) - : $value; - - array_push($encoded, $key . '=' . $value); - } - return implode("|", $encoded); - } else { - return $array; - } - } - - private static function is_assoc($array) { - if (!is_array($array)) return FALSE; - return $array != array_values($array); - } - - private static function generate_base_transformation($base_transformation) { - $options = is_array($base_transformation) ? $base_transformation : array("transformation"=>$base_transformation); - return Cloudinary::generate_transformation_string($options); - } - - // Warning: $options are being destructively updated! - public static function generate_transformation_string(&$options=array()) { - $generate_base_transformation = "Cloudinary::generate_base_transformation"; - if (is_string($options)) { - return $options; - } - if ($options == array_values($options)) { - return implode("/", array_map($generate_base_transformation, $options)); - } - - $responsive_width = Cloudinary::option_consume($options, "responsive_width", Cloudinary::config_get("responsive_width")); - - $size = Cloudinary::option_consume($options, "size"); - if ($size) list($options["width"], $options["height"]) = preg_split("/x/", $size); - - $width = Cloudinary::option_get($options, "width"); - $height = Cloudinary::option_get($options, "height"); - - $has_layer = Cloudinary::option_get($options, "underlay") || Cloudinary::option_get($options, "overlay"); - $angle = implode(Cloudinary::build_array(Cloudinary::option_consume($options, "angle")), "."); - $crop = Cloudinary::option_consume($options, "crop"); - - $no_html_sizes = $has_layer || !empty($angle) || $crop == "fit" || $crop == "limit" || $responsive_width; - - if (strlen($width) == 0 || $width && (substr($width, 0, 4) == "auto" || floatval($width) < 1 || $no_html_sizes)) unset($options["width"]); - if (strlen($height) == 0 || $height && (floatval($height) < 1 || $no_html_sizes)) unset($options["height"]); - - $background = Cloudinary::option_consume($options, "background"); - if ($background) $background = preg_replace("/^#/", 'rgb:', $background); - $color = Cloudinary::option_consume($options, "color"); - if ($color) $color = preg_replace("/^#/", 'rgb:', $color); - - $base_transformations = Cloudinary::build_array(Cloudinary::option_consume($options, "transformation")); - if (count(array_filter($base_transformations, "is_array")) > 0) { - $base_transformations = array_map($generate_base_transformation, $base_transformations); - $named_transformation = ""; - } else { - $named_transformation = implode(".", $base_transformations); - $base_transformations = array(); - } - - $effect = Cloudinary::option_consume($options, "effect"); - if (is_array($effect)) $effect = implode(":", $effect); - - $border = Cloudinary::process_border(Cloudinary::option_consume($options, "border")); - - $flags = implode(Cloudinary::build_array(Cloudinary::option_consume($options, "flags")), "."); - $dpr = Cloudinary::option_consume($options, "dpr", Cloudinary::config_get("dpr")); - - $duration = Cloudinary::norm_range_value(Cloudinary::option_consume($options, "duration")); - $start_offset = Cloudinary::norm_range_value(Cloudinary::option_consume($options, "start_offset")); - $end_offset = Cloudinary::norm_range_value(Cloudinary::option_consume($options, "end_offset")); - $offset = Cloudinary::split_range(Cloudinary::option_consume($options, "offset")); - if (!empty($offset)) { - $start_offset = Cloudinary::norm_range_value($offset[0]); - $end_offset = Cloudinary::norm_range_value($offset[1]); - } - - $video_codec = Cloudinary::process_video_codec_param(Cloudinary::option_consume($options, "video_codec")); - - $overlay = Cloudinary::process_layer(Cloudinary::option_consume($options, "overlay"), "overlay"); - $underlay = Cloudinary::process_layer(Cloudinary::option_consume($options, "underlay"), "underlay"); - $if = Cloudinary::process_if(Cloudinary::option_consume($options, "if")); - - $aspect_ratio = Cloudinary::option_consume($options, "aspect_ratio"); - $opacity = Cloudinary::option_consume($options, "opacity"); - $quality = Cloudinary::option_consume($options, "quality"); - $radius = Cloudinary::option_consume($options, "radius"); - $x = Cloudinary::option_consume($options, "x"); - $y = Cloudinary::option_consume($options, "y"); - $zoom = Cloudinary::option_consume($options, "zoom"); - - $params = array( - "a" => self::normalize_expression($angle), - "ar" => self::normalize_expression($aspect_ratio), - "b" => $background, - "bo" => $border, - "c" => $crop, - "co" => $color, - "dpr" => self::normalize_expression($dpr), - "du" => $duration, - "e" => self::normalize_expression($effect), - "eo" => $end_offset, - "fl" => $flags, - "h" => self::normalize_expression($height), - "l" => $overlay, - "o" => self::normalize_expression($opacity), - "q" => self::normalize_expression($quality), - "r" => self::normalize_expression($radius), - "so" => $start_offset, - "t" => $named_transformation, - "u" => $underlay, - "vc" => $video_codec, - "w" => self::normalize_expression($width), - "x" => self::normalize_expression($x), - "y" => self::normalize_expression($y), - "z" => self::normalize_expression($zoom), - ); - - $simple_params = array( - "ac" => "audio_codec", - "af" => "audio_frequency", - "br" => "bit_rate", - "cs" => "color_space", - "d" => "default_image", - "dl" => "delay", - "dn" => "density", - "f" => "fetch_format", - "g" => "gravity", - "p" => "prefix", - "pg" => "page", - "vs" => "video_sampling", - ); - - foreach ($simple_params as $param=>$option) { - $params[$param] = Cloudinary::option_consume($options, $option); - } - - $variables = !empty($options["variables"]) ? $options["variables"] : []; - - $var_params = []; - foreach($options as $key => $value) { - if (preg_match('/^\$/', $key)) { - $var_params[] = $key . '_' . self::normalize_expression((string)$value); - } - } - - sort($var_params); - - if (!empty($variables)) { - foreach($variables as $key => $value) { - $var_params[] = $key . '_' . self::normalize_expression((string)$value); - } - } - - $variables = join(',', $var_params); - - - $param_filter = function($value) { return $value === 0 || $value === '0' || trim($value) == true; }; - $params = array_filter($params, $param_filter); - ksort($params); - if (isset($if)) { - $if = 'if_' . $if; - } - $join_pair = function($key, $value) { return $key . "_" . $value; }; - $transformation = implode(",", array_map($join_pair, array_keys($params), array_values($params))); - $raw_transformation = Cloudinary::option_consume($options, "raw_transformation"); - $transformation = implode(",", array_filter(array($if, $variables, $transformation, $raw_transformation))); - array_push($base_transformations, $transformation); - if ($responsive_width) { - $responsive_width_transformation = Cloudinary::config_get("responsive_width_transformation", Cloudinary::$DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION); - array_push($base_transformations, Cloudinary::generate_transformation_string($responsive_width_transformation)); - } - if (substr($width, 0, 4) == "auto" || $responsive_width) { - $options["responsive"] = true; - } - if (substr($dpr, 0, 4) == "auto") { - $options["hidpi"] = true; - } - return implode("/", array_filter($base_transformations)); - } - - private static $LAYER_KEYWORD_PARAMS = array( - "font_weight"=>"normal", "font_style"=>"normal", "text_decoration"=>"none", "text_align"=>NULL, "stroke"=>"none" - ); - - private static function text_style( $layer, $layer_parameter) { - $font_family = Cloudinary::option_get($layer, "font_family"); - $font_size = Cloudinary::option_get($layer, "font_size"); - $keywords = array(); - foreach (Cloudinary::$LAYER_KEYWORD_PARAMS as $attr=>$default_value) { - $attr_value = Cloudinary::option_get($layer, $attr, $default_value); - if ($attr_value != $default_value) { - array_push($keywords, $attr_value); - } - } - $letter_spacing = Cloudinary::option_get($layer, "letter_spacing"); - if ($letter_spacing != NULL) { - array_push($keywords, "letter_spacing_$letter_spacing"); - } - $line_spacing = Cloudinary::option_get($layer, "line_spacing"); - if ($line_spacing != NULL) { - array_push($keywords, "line_spacing_$line_spacing"); - } - $has_text_options = $font_size != NULL || $font_family != NULL || !empty($keywords); - if (!$has_text_options) { - return NULL; - } - if ($font_family == NULL) { - throw new InvalidArgumentException("Must supply font_family for text in $layer_parameter"); - } - if ($font_size == NULL) { - throw new InvalidArgumentException("Must supply font_size for text in $layer_parameter"); - } - array_unshift($keywords, $font_size); - array_unshift($keywords, $font_family); - return implode("_", array_filter($keywords, 'Cloudinary::is_not_null')); - } - - /** - * Handle overlays. - * Overlay properties can came as array or as string. - * @param $layer - * @param $layer_parameter - * @return string - */ - private static function process_layer($layer, $layer_parameter) { - // When overlay is array. - if (is_array($layer)) { - $resource_type = Cloudinary::option_get($layer, "resource_type"); - $type = Cloudinary::option_get($layer, "type"); - $text = Cloudinary::option_get($layer, "text"); - $fetch = Cloudinary::option_get($layer, "fetch"); - $text_style = NULL; - $public_id = Cloudinary::option_get($layer, "public_id"); - $format = Cloudinary::option_get($layer, "format"); - $components = array(); - - if ($public_id != NULL){ - $public_id = str_replace("/", ":", $public_id); - if($format != NULL) $public_id = $public_id . "." . $format; - } - - // Fetch overlay. - if (!empty($fetch) || $resource_type === "fetch") { - $public_id = NULL; - $resource_type = "fetch"; - $fetch = base64_encode($fetch); - } - - // Text overlay. - elseif (!empty($text) || $resource_type === "text") { - $resource_type = "text"; - $type = NULL; // type is ignored for text layers - $text_style = Cloudinary::text_style($layer, $layer_parameter); #FIXME duplicate - if ($text != NULL) { - if (!($public_id != NULL xor $text_style != NULL)) { - throw new InvalidArgumentException("Must supply either style parameters or a public_id when providing text parameter in a text $layer_parameter"); - } - $escaped = Cloudinary::smart_escape($text); - $escaped = str_replace("%2C", "%252C", $escaped); - $escaped = str_replace("/", "%252F", $escaped); - # Don't encode interpolation expressions e.g. $(variable) - preg_match_all('/\$\([a-zA-Z]\w+\)/', $text, $matches); - foreach ($matches[0] as $match) { - $escaped_match = Cloudinary::smart_escape($match); - $escaped = str_replace($escaped_match, $match, $escaped); - } - - $text = $escaped; - } - } else { - if ($public_id == NULL) { - throw new InvalidArgumentException("Must supply public_id for $resource_type $layer_parameter"); - } - if ($resource_type == "subtitles") { - $text_style = Cloudinary::text_style($layer, $layer_parameter); - } - } - - // Build a components array. - if($resource_type != "image") array_push($components, $resource_type); - if($type != "upload") array_push($components, $type); - array_push($components, $text_style); - array_push($components, $public_id); - array_push($components, $text); - array_push($components, $fetch); - - // Build a valid overlay string. - $layer = implode(":", array_filter($components, 'Cloudinary::is_not_null')); - } - - // Handle fetch overlay from string definition. - elseif (substr($layer, 0, strlen('fetch:')) === 'fetch:') { - $url = substr($layer, strlen('fetch:')); - $b64 = base64_encode($url); - $layer = 'fetch:' . $b64; - } - - return $layer; - } - - private static $CONDITIONAL_OPERATORS = array( - "=" => 'eq', - "!=" => 'ne', - "<" => 'lt', - ">" => 'gt', - "<=" => 'lte', - ">=" => 'gte', - "&&" => 'and', - "||" => 'or', - "*" => 'mul', - "/" => 'div', - "+" => 'add', - "-" => 'sub' - ); - private static $PREDEFINED_VARS = array( - "aspect_ratio" => "ar", - "current_page" => "cp", - "face_count" => "fc", - "height" => "h", - "initial_aspect_ratio" => "iar", - "initial_height" => "ih", - "initial_width" => "iw", - "page_count" => "pc", - "page_x" => "px", - "page_y" => "py", - "tags" => "tags", - "width" => "w" - ); - - private static function translate_if( $source ) - { - if (isset(self::$CONDITIONAL_OPERATORS[$source[0]])) { - return self::$CONDITIONAL_OPERATORS[$source[0]]; - } elseif (isset(self::$PREDEFINED_VARS[$source[0]])) { - return self::$PREDEFINED_VARS[$source[0]]; - } else { - return $source[0]; - } - } - - private static $IF_REPLACE_RE; - - private static function process_if($if) { - $if = self::normalize_expression($if); - return $if; - } - - private static function normalize_expression($exp) { - if (is_float($exp)) { - return number_format($exp, 1); - } - if (preg_match('/^!.+!$/', $exp)) { - return $exp; - } else { - if (empty(self::$IF_REPLACE_RE)) { - self::$IF_REPLACE_RE = '/((\|\||>=|<=|&&|!=|>|=|<|\/|\-|\+|\*)(?=[ _])|' . implode('|', array_keys(self::$PREDEFINED_VARS)) . ')/'; - } - if (isset($exp)) { - $exp = preg_replace('/[ _]+/', '_', $exp); - $exp = preg_replace_callback(self::$IF_REPLACE_RE, array("Cloudinary", "translate_if"), $exp); - } - return $exp; - } - - } - - private static function process_border($border) { - if (is_array($border)) { - $border_width = Cloudinary::option_get($border, "width", "2"); - $border_color = preg_replace("/^#/", 'rgb:', Cloudinary::option_get($border, "color", "black")); - $border = $border_width . "px_solid_" . $border_color; - } - return $border; - } - - private static function split_range($range) { - if (is_array($range) && count($range) >= 2) { - return array($range[0], end($range)); - } else if (is_string($range) && preg_match(Cloudinary::RANGE_RE, $range) == 1) { - return explode("..", $range, 2); - } else { - return NULL; - } - } - - private static function norm_range_value($value) { - if (empty($value)) { - return NULL; - } - - preg_match(Cloudinary::RANGE_VALUE_RE, $value, $matches); - - if (empty($matches)) { - return NULL; - } - - $modifier = ''; - if (!empty($matches['modifier'])) { - $modifier = 'p'; - } - return $matches['value'] . $modifier; - } - - private static function process_video_codec_param($param) { - $out_param = $param; - if (is_array($out_param)) { - $out_param = $param['codec']; - if (array_key_exists('profile', $param)) { - $out_param = $out_param . ':' . $param['profile']; - if (array_key_exists('level', $param)) { - $out_param = $out_param . ':' . $param['level']; - } - } - } - return $out_param; - } - - // Warning: $options are being destructively updated! - public static function cloudinary_url($source, &$options=array()) { - $source = self::check_cloudinary_field($source, $options); - $type = Cloudinary::option_consume($options, "type", "upload"); - - if ($type == "fetch" && !isset($options["fetch_format"])) { - $options["fetch_format"] = Cloudinary::option_consume($options, "format"); - } - $transformation = Cloudinary::generate_transformation_string($options); - - $resource_type = Cloudinary::option_consume($options, "resource_type", "image"); - $version = Cloudinary::option_consume($options, "version"); - $format = Cloudinary::option_consume($options, "format"); - - $cloud_name = Cloudinary::option_consume($options, "cloud_name", Cloudinary::config_get("cloud_name")); - if (!$cloud_name) throw new InvalidArgumentException("Must supply cloud_name in tag or in configuration"); - $secure = Cloudinary::option_consume($options, "secure", Cloudinary::config_get("secure")); - $private_cdn = Cloudinary::option_consume($options, "private_cdn", Cloudinary::config_get("private_cdn")); - $secure_distribution = Cloudinary::option_consume($options, "secure_distribution", Cloudinary::config_get("secure_distribution")); - $cdn_subdomain = Cloudinary::option_consume($options, "cdn_subdomain", Cloudinary::config_get("cdn_subdomain")); - $secure_cdn_subdomain = Cloudinary::option_consume($options, "secure_cdn_subdomain", Cloudinary::config_get("secure_cdn_subdomain")); - $cname = Cloudinary::option_consume($options, "cname", Cloudinary::config_get("cname")); - $shorten = Cloudinary::option_consume($options, "shorten", Cloudinary::config_get("shorten")); - $sign_url = Cloudinary::option_consume($options, "sign_url", Cloudinary::config_get("sign_url")); - $api_secret = Cloudinary::option_consume($options, "api_secret", Cloudinary::config_get("api_secret")); - $url_suffix = Cloudinary::option_consume($options, "url_suffix", Cloudinary::config_get("url_suffix")); - $use_root_path = Cloudinary::option_consume($options, "use_root_path", Cloudinary::config_get("use_root_path")); - $auth_token = Cloudinary::option_consume($options, "auth_token"); - if (is_array($auth_token) ) { - $auth_token = array_merge(self::config_get("auth_token", array()), $auth_token); - } elseif (is_null($auth_token)) { - $auth_token = self::config_get("auth_token"); - } - - if (!$private_cdn and !empty($url_suffix)) { - throw new InvalidArgumentException("URL Suffix only supported in private CDN"); - } - - if (!$source) return $source; - - if (preg_match("/^https?:\//i", $source)) { - if ($type == "upload") return $source; - } - - $resource_type_and_type = Cloudinary::finalize_resource_type($resource_type, $type, $url_suffix, $use_root_path, $shorten); - $sources = Cloudinary::finalize_source($source, $format, $url_suffix); - $source = $sources["source"]; - $source_to_sign = $sources["source_to_sign"]; - - if (strpos($source_to_sign, "/") && !preg_match("/^https?:\//", $source_to_sign) && !preg_match("/^v[0-9]+/", $source_to_sign) && empty($version)) { - $version = "1"; - } - $version = $version ? "v" . $version : NULL; - - $signature = NULL; - if ($sign_url && !$auth_token) { - $to_sign = implode("/", array_filter(array($transformation, $source_to_sign))); - $signature = str_replace(array('+','/','='), array('-','_',''), base64_encode(sha1($to_sign . $api_secret, TRUE))); - $signature = 's--' . substr($signature, 0, 8) . '--'; - } - - $prefix = Cloudinary::unsigned_download_url_prefix($source, $cloud_name, $private_cdn, $cdn_subdomain, $secure_cdn_subdomain, - $cname, $secure, $secure_distribution); - - $source = preg_replace( "/([^:])\/+/", "$1/", implode( "/", array_filter( array( - $prefix, - $resource_type_and_type, - $signature, - $transformation, - $version, - $source - ) ) ) ); - - if( $sign_url && $auth_token) { - $path = parse_url($source, PHP_URL_PATH); - $token = \Cloudinary\AuthToken::generate(array_merge($auth_token, array( "url" => $path))); - $source = $source . "?" . $token; - } - return $source; - } - - private static function finalize_source($source, $format, $url_suffix) { - $source = preg_replace('/([^:])\/\//', '$1/', $source); - if (preg_match('/^https?:\//i', $source)) { - $source = Cloudinary::smart_escape($source); - $source_to_sign = $source; - } else { - $source = Cloudinary::smart_escape(rawurldecode($source)); - $source_to_sign = $source; - if (!empty($url_suffix)) { - if (preg_match('/[\.\/]/i', $url_suffix)) throw new InvalidArgumentException("url_suffix should not include . or /"); - $source = $source . '/' . $url_suffix; - } - if (!empty($format)) { - $source = $source . '.' . $format ; - $source_to_sign = $source_to_sign . '.' . $format ; - } - } - return array("source" => $source, "source_to_sign" => $source_to_sign); - } - - private static function finalize_resource_type($resource_type, $type, $url_suffix, $use_root_path, $shorten) { - if (empty($type)) { - $type = "upload"; - } - - if (!empty($url_suffix)) { - if ($resource_type == "image" && $type == "upload") { - $resource_type = "images"; - $type = NULL; - } else if ($resource_type == "image" && $type == "private") { - $resource_type = "private_images"; - $type = NULL; - } else if ($resource_type == "raw" && $type == "upload") { - $resource_type = "files"; - $type = NULL; - } else { - throw new InvalidArgumentException("URL Suffix only supported for image/upload, image/private and raw/upload"); - } - } - - if ($use_root_path) { - if (($resource_type == "image" && $type == "upload") || ($resource_type == "images" && empty($type))) { - $resource_type = NULL; - $type = NULL; - } else { - throw new InvalidArgumentException("Root path only supported for image/upload"); - } - } - if ($shorten && $resource_type == "image" && $type == "upload") { - $resource_type = "iu"; - $type = NULL; - } - $out = ""; - if (!empty($resource_type)) { - $out = $resource_type; - } - if (!empty($type)) { - $out = $out . '/' . $type; - } - return $out; - } - - // cdn_subdomain and secure_cdn_subdomain - // 1) Customers in shared distribution (e.g. res.cloudinary.com) - // if cdn_domain is true uses res-[1-5].cloudinary.com for both http and https. Setting secure_cdn_subdomain to false disables this for https. - // 2) Customers with private cdn - // if cdn_domain is true uses cloudname-res-[1-5].cloudinary.com for http - // if secure_cdn_domain is true uses cloudname-res-[1-5].cloudinary.com for https (please contact support if you require this) - // 3) Customers with cname - // if cdn_domain is true uses a[1-5].cname for http. For https, uses the same naming scheme as 1 for shared distribution and as 2 for private distribution. - private static function unsigned_download_url_prefix($source, $cloud_name, $private_cdn, $cdn_subdomain, $secure_cdn_subdomain, $cname, $secure, $secure_distribution) { - $shared_domain = !$private_cdn; - $prefix = NULL; - if ($secure) { - if (empty($secure_distribution) || $secure_distribution == Cloudinary::OLD_AKAMAI_SHARED_CDN) { - $secure_distribution = $private_cdn ? $cloud_name . '-res.cloudinary.com' : Cloudinary::SHARED_CDN; - } - - if (empty($shared_domain)) { - $shared_domain = ($secure_distribution == Cloudinary::SHARED_CDN); - } - - if (is_null($secure_cdn_subdomain) && $shared_domain) { - $secure_cdn_subdomain = $cdn_subdomain ; - } - - if ($secure_cdn_subdomain) { - $secure_distribution = str_replace('res.cloudinary.com', "res-" . Cloudinary::domain_shard($source) . ".cloudinary.com", $secure_distribution); - } - - $prefix = "https://" . $secure_distribution; - } else if ($cname) { - $subdomain = $cdn_subdomain ? "a" . Cloudinary::domain_shard($source) . '.' : ""; - $prefix = "http://" . $subdomain . $cname; - } else { - $host = implode(array($private_cdn ? $cloud_name . "-" : "", "res", $cdn_subdomain ? "-" . Cloudinary::domain_shard($source) : "", ".cloudinary.com")); - $prefix = "http://" . $host; - } - if ($shared_domain) { - $prefix = $prefix . '/' . $cloud_name; - } - return $prefix; - } - - private static function domain_shard($source) { - return (((crc32($source) % 5) + 5) % 5 + 1); - } - - // [/][/][v/][.][#] - // Warning: $options are being destructively updated! - public static function check_cloudinary_field($source, &$options=array()) { - $IDENTIFIER_RE = "~" . - "^(?:([^/]+)/)??(?:([^/]+)/)??(?:(?:v(\\d+)/)(?:([^#]+)/)?)?" . - "([^#/]+?)(?:\\.([^.#/]+))?(?:#([^/]+))?$" . - "~"; - $matches = array(); - if (!(is_object($source) && method_exists($source, 'identifier'))) { - return $source; - } - $identifier = $source->identifier(); - if (!$identifier || strstr(':', $identifier) !== false || !preg_match($IDENTIFIER_RE, $identifier, $matches)) { - return $source; - } - $optionNames = array('resource_type', 'type', 'version', 'folder', 'public_id', 'format'); - foreach ($optionNames as $index => $optionName) { - if (@$matches[$index+1]) { - $options[$optionName] = $matches[$index+1]; - } - } - return Cloudinary::option_consume($options, 'public_id'); - } - - // Based on http://stackoverflow.com/a/1734255/526985 - private static function smart_escape($str) { - $revert = array('%21'=>'!', '%2A'=>'*', '%27'=>"'", '%3A'=>':', '%2F'=>'/'); - return strtr(rawurlencode($str), $revert); - } - - public static function cloudinary_api_url($action = 'upload', $options = array()) { - $cloudinary = Cloudinary::option_get($options, "upload_prefix", Cloudinary::config_get("upload_prefix", "https://api.cloudinary.com")); - $cloud_name = Cloudinary::option_get($options, "cloud_name", Cloudinary::config_get("cloud_name")); - if (!$cloud_name) throw new InvalidArgumentException("Must supply cloud_name in options or in configuration"); - $resource_type = Cloudinary::option_get($options, "resource_type", "image"); - return implode("/", array($cloudinary, "v1_1", $cloud_name, $resource_type, $action)); - } - - public static function random_public_id() { - return substr(sha1(uniqid(Cloudinary::config_get("api_secret", "") . mt_rand())), 0, 16); - } - - public static function signed_preloaded_image($result) { - return $result["resource_type"] . "/upload/v" . $result["version"] . "/" . $result["public_id"] . - (isset($result["format"]) ? "." . $result["format"] : "") . "#" . $result["signature"]; - } - - # Utility method that uses the deprecated ZIP download API. - # @deprecated Replaced by {download_zip_url} that uses the more advanced and robust archive generation and download API - public static function zip_download_url($tag, $options=array()) { - $params = array("timestamp"=>time(), "tag"=>$tag, "transformation" => \Cloudinary::generate_transformation_string($options)); - $params = Cloudinary::sign_request($params, $options); - return Cloudinary::cloudinary_api_url("download_tag.zip", $options) . "?" . http_build_query($params); - } - - - # Returns a URL that when invokes creates an archive and returns it. - # @param options [Hash] - # @option options [String] resource_type The resource type of files to include in the archive. Must be one of image | video | raw - # @option options [String] type (upload) The specific file type of resources upload|private|authenticated - # @option options [String|Array] tags (nil) list of tags to include in the archive - # @option options [String|Array] public_ids (nil) list of public_ids to include in the archive - # @option options [String|Array] prefixes (nil) Optional list of prefixes of public IDs (e.g., folders). - # @option options [String|Array] transformations Optional list of transformations. - # The derived images of the given transformations are included in the archive. Using the string representation of - # multiple chained transformations as we use for the 'eager' upload parameter. - # @option options [String] mode (create) return the generated archive file or to store it as a raw resource and - # return a JSON with URLs for accessing the archive. Possible values download, create - # @option options [String] target_format (zip) - # @option options [String] target_public_id Optional public ID of the generated raw resource. - # Relevant only for the create mode. If not specified, random public ID is generated. - # @option options [boolean] flatten_folders (false) If true, flatten public IDs with folders to be in the root of the archive. - # Add numeric counter to the file name in case of a name conflict. - # @option options [boolean] flatten_transformations (false) If true, and multiple transformations are given, - # flatten the folder structure of derived images and store the transformation details on the file name instead. - # @option options [boolean] use_original_filename Use the original file name of included images (if available) instead of the public ID. - # @option options [boolean] async (false) If true, return immediately and perform the archive creation in the background. - # Relevant only for the create mode. - # @option options [String] notification_url Optional URL to send an HTTP post request (webhook) when the archive creation is completed. - # @option options [String|Array \Cloudinary::option_get($options, "allow_missing"), - "async" => \Cloudinary::option_get($options, "async"), - "expires_at" => \Cloudinary::option_get($options, "expires_at"), - "flatten_folders" => \Cloudinary::option_get($options, "flatten_folders"), - "flatten_transformations" => \Cloudinary::option_get($options, "flatten_transformations"), - "keep_derived" => \Cloudinary::option_get($options, "keep_derived"), - "mode" => \Cloudinary::option_get($options, "mode"), - "notification_url" => \Cloudinary::option_get($options, "notification_url"), - "phash" => \Cloudinary::option_get($options, "phash"), - "prefixes" => \Cloudinary::build_array(\Cloudinary::option_get($options, "prefixes")), - "public_ids" => \Cloudinary::build_array(\Cloudinary::option_get($options, "public_ids")), - "skip_transformation_name" => \Cloudinary::option_get($options, "skip_transformation_name"), - "tags" => \Cloudinary::build_array(\Cloudinary::option_get($options, "tags")), - "target_format" => \Cloudinary::option_get($options, "target_format"), - "target_public_id" => \Cloudinary::option_get($options, "target_public_id"), - "target_tags" => \Cloudinary::build_array(\Cloudinary::option_get($options, "target_tags")), - "timestamp" => time(), - "transformations" => \Cloudinary::build_eager(\Cloudinary::option_get($options, "transformations")), - "type" => \Cloudinary::option_get($options, "type"), - "use_original_filename" => \Cloudinary::option_get($options, "use_original_filename"), - ); - array_walk($params, function (&$value, $key){ $value = (is_bool($value) ? ($value ? "1" : "0") : $value);}); - return array_filter($params,function($v){ return !is_null($v) && ($v !== "" );}); - } - - public static function build_eager($transformations) { - $eager = array(); - foreach (\Cloudinary::build_array($transformations) as $trans) { - $transformation = $trans; - $format = \Cloudinary::option_consume($transformation, "format"); - $single_eager = implode("/", array_filter(array(\Cloudinary::generate_transformation_string($transformation), $format))); - array_push($eager, $single_eager); - } - return implode("|", $eager); - } - - public static function private_download_url($public_id, $format, $options = array()) { - $cloudinary_params = Cloudinary::sign_request(array( - "timestamp" => time(), - "public_id" => $public_id, - "format" => $format, - "type" => Cloudinary::option_get($options, "type"), - "attachment" => Cloudinary::option_get($options, "attachment"), - "expires_at" => Cloudinary::option_get($options, "expires_at") - ), $options); - - return Cloudinary::cloudinary_api_url("download", $options) . "?" . http_build_query($cloudinary_params); - } - - public static function sign_request($params, &$options) { - $api_key = Cloudinary::option_get($options, "api_key", Cloudinary::config_get("api_key")); - if (!$api_key) throw new \InvalidArgumentException("Must supply api_key"); - $api_secret = Cloudinary::option_get($options, "api_secret", Cloudinary::config_get("api_secret")); - if (!$api_secret) throw new \InvalidArgumentException("Must supply api_secret"); - - # Remove blank parameters - $params = array_filter($params, function($v){ return isset($v) && $v !== "";}); - - $params["signature"] = Cloudinary::api_sign_request($params, $api_secret); - $params["api_key"] = $api_key; - - return $params; - } - - public static function api_sign_request($params_to_sign, $api_secret) { - $params = array(); - foreach ($params_to_sign as $param => $value) { - if (isset($value) && $value !== "") { - if (!is_array($value)) { - $params[$param] = $value; - } else if (count($value) > 0) { - $params[$param] = implode(",", $value); - } - } - } - ksort($params); - $join_pair = function($key, $value) { return $key . "=" . $value; }; - $to_sign = implode("&", array_map($join_pair, array_keys($params), array_values($params))); - return sha1($to_sign . $api_secret); - } - - public static function html_attrs($options, $only = NULL) { - $attrs = array(); - foreach($options as $k => $v) { - $key = $k; - $value = $v; - if (is_int($k)) { - $key = $v; - $value = ""; - } - if (is_array($only) && array_search($key, $only) !== FALSE || !is_array($only)) { - $attrs[$key] = $value; - } - } - ksort($attrs); - - $join_pair = function($key, $value) { - $out = $key; - if (!empty($value)) { - $out .= '=\'' . $value . '\''; - } - return $out; - }; - return implode(" ", array_map($join_pair, array_keys($attrs), array_values($attrs))); - } -} - -require_once(join(DIRECTORY_SEPARATOR, array(dirname(__FILE__), 'Helpers.php'))); diff --git a/lib/Cloudinary/CloudinaryField.php b/lib/Cloudinary/CloudinaryField.php deleted file mode 100644 index 455e207..0000000 --- a/lib/Cloudinary/CloudinaryField.php +++ /dev/null @@ -1,54 +0,0 @@ -_identifier = $identifier; - } - - public function __toString() { - return explode("#", $this->identifier())[0]; - } - - public function identifier() { - return $this->_identifier; - } - - public function url($options = array()) { - if (!$this->_identifier) { - // TODO: Error? - return; - } - return cloudinary_url($this, $options); - } - - public function upload($file, $options = array()) { - $options['return_error'] = false; - $ret = \Cloudinary\Uploader::upload($file, $options); - $preloaded = new \Cloudinary\PreloadedFile(\Cloudinary::signed_preloaded_image($ret)); - if ($this->verifyUpload && !$preloaded.is_valid()) { - throw new \Exception("Error! Couldn't verify cloudinary response!"); - } - $this->_identifier = $preloaded->extended_identifier(); - } - - public function delete() { - $options['return_error'] = false; - $ret = \Cloudinary\Uploader::destroy($this->_identifier); - unset($this->_identifier); - } - - public function verify() { - $preloaded = new \Cloudinary\PreloadedFile($this->_identifier); - return $preloaded->is_valid(); - } -} diff --git a/lib/Cloudinary/Helpers.php b/lib/Cloudinary/Helpers.php deleted file mode 100644 index 8403916..0000000 --- a/lib/Cloudinary/Helpers.php +++ /dev/null @@ -1,249 +0,0 @@ -TRUE, "upload_preset"=>$upload_preset))); - } - - function cl_image_upload_tag($field, $options = array()) - { - return cl_upload_tag($field, $options); - } - - function cl_upload_tag($field, $options = array()) - { - $html_options = Cloudinary::option_get($options, "html", array()); - - $classes = array("cloudinary-fileupload"); - if (isset($html_options["class"])) { - array_unshift($classes, Cloudinary::option_consume($html_options, "class")); - } - $tag_options = array_merge($html_options, array("type" => "file", "name" => "file", - "data-url" => cl_upload_url($options), - "data-form-data" => cl_upload_tag_params($options), - "data-cloudinary-field" => $field, - "class" => implode(" ", $classes), - )); - if (array_key_exists('chunk_size', $options)) $tag_options['data-max-chunk-size'] = $options['chunk_size']; - return ''; - } - - function cl_form_tag($callback_url, $options = array()) - { - $form_options = Cloudinary::option_get($options, "form", array()); - - $options["callback_url"] = $callback_url; - - $params = Cloudinary\Uploader::build_upload_params($options); - $params = Cloudinary::sign_request($params, $options); - - $api_url = Cloudinary::cloudinary_api_url("upload", $options); - - $form = "
\n"; - foreach ($params as $key => $value) { - $form .= " $key, "value" => $value, "type" => "hidden")) . "/>\n"; - } - $form .= "
\n"; - - return $form; - } - - // Examples - // cl_image_tag("israel.png", array("width"=>100, "height"=>100, "alt"=>"hello") # W/H are not sent to cloudinary - // cl_image_tag("israel.png", array("width"=>100, "height"=>100, "alt"=>"hello", "crop"=>"fit") # W/H are sent to cloudinary - function cl_image_tag($source, $options = array()) { - $source = cloudinary_url_internal($source, $options); - if (isset($options["html_width"])) $options["width"] = Cloudinary::option_consume($options, "html_width"); - if (isset($options["html_height"])) $options["height"] = Cloudinary::option_consume($options, "html_height"); - - $client_hints = Cloudinary::option_consume($options, "client_hints", Cloudinary::config_get("client_hints")); - $responsive = Cloudinary::option_consume($options, "responsive"); - $hidpi = Cloudinary::option_consume($options, "hidpi"); - if (($responsive || $hidpi) && !$client_hints) { - $options["data-src"] = $source; - $classes = array($responsive ? "cld-responsive" : "cld-hidpi"); - $current_class = Cloudinary::option_consume($options, "class"); - if ($current_class) array_unshift($classes, $current_class); - $options["class"] = implode(" ", $classes); - $source = Cloudinary::option_consume($options, "responsive_placeholder", Cloudinary::config_get("responsive_placeholder")); - if ($source == "blank") { - $source = Cloudinary::BLANK; - } - } - $html = ""; - return $html; - } - - function fetch_image_tag($url, $options = array()) { - $options["type"] = "fetch"; - return cl_image_tag($url, $options); - } - - function facebook_profile_image_tag($profile, $options = array()) { - $options["type"] = "facebook"; - return cl_image_tag($profile, $options); - } - - function gravatar_profile_image_tag($email, $options = array()) { - $options["type"] = "gravatar"; - $options["format"] = "jpg"; - return cl_image_tag(hash('sha256', strtolower(trim($email))), $options); - } - - function twitter_profile_image_tag($profile, $options = array()) { - $options["type"] = "twitter"; - return cl_image_tag($profile, $options); - } - - function twitter_name_profile_image_tag($profile, $options = array()) { - $options["type"] = "twitter_name"; - return cl_image_tag($profile, $options); - } - - function cloudinary_js_config() { - $params = array(); - foreach (Cloudinary::$JS_CONFIG_PARAMS as $param) { - $value = Cloudinary::config_get($param); - if ($value) $params[$param] = $value; - } - return "\n"; - } - - function cloudinary_url($source, $options = array()) { - return cloudinary_url_internal($source, $options); - } - function cloudinary_url_internal($source, &$options = array()) { - if (!isset($options["secure"])) { - $options["secure"] = ( isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ) - || ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ); - } - - return Cloudinary::cloudinary_url($source, $options); - } - - function cl_sprite_url($tag, $options = array()) { - if (substr($tag, -strlen(".css")) != ".css") { - $options["format"] = "css"; - } - $options["type"] = "sprite"; - return cloudinary_url_internal($tag, $options); - } - - function cl_sprite_tag($tag, $options = array()) { - return ""; - } - - function default_poster_options() { return array( 'format' => 'jpg', 'resource_type' => 'video' ); } - function default_source_types() { return array('webm', 'mp4', 'ogv'); } - # Returns a url for the given source with +options+ - function cl_video_path($source, $options = array()) { - $options = array_merge( array('resource_type' => 'video'), $options); - return cloudinary_url_internal($source, $options); - } - - # Returns an HTML img tag with the thumbnail for the given video +source+ and +options+ - function cl_video_thumbnail_tag($source, $options = array()) { - return cl_image_tag($source, array_merge(default_poster_options(),$options)); - } - - # Returns a url for the thumbnail for the given video +source+ and +options+ - function cl_video_thumbnail_path($source, $options = array()) { - $options = array_merge(default_poster_options(), $options); - return cloudinary_url_internal($source, $options); - } - - # Creates an HTML video tag for the provided +source+ - # - # ==== Options - # * source_types - Specify which source type the tag should include. defaults to webm, mp4 and ogv. - # * source_transformation - specific transformations to use for a specific source type. - # * poster - override default thumbnail: - # * url: provide an ad hoc url - # * options: with specific poster transformations and/or Cloudinary +:public_id+ - # - # ==== Examples - # cl_video_tag("mymovie.mp4") - # cl_video_tag("mymovie.mp4", array('source_types' => 'webm')) - # cl_video_tag("mymovie.ogv", array('poster' => "myspecialplaceholder.jpg")) - # cl_video_tag("mymovie.webm", array('source_types' => array('webm', 'mp4'), 'poster' => array('effect' => 'sepia'))) - function cl_video_tag($source, $options = array()) { - $source = preg_replace('/\.(' . implode('|', default_source_types()) . ')$/', '', $source); - - $source_types = Cloudinary::option_consume($options, 'source_types', array()); - $source_transformation = Cloudinary::option_consume($options, 'source_transformation', array()); - $fallback = Cloudinary::option_consume($options, 'fallback_content', ''); - - if (empty($source_types)) { - $source_types = default_source_types(); - } - $video_options = $options; - - if (array_key_exists('poster', $video_options)) { - if (is_array($video_options['poster'])) { - if (array_key_exists('public_id', $video_options['poster'])) { - $video_options['poster'] = cloudinary_url_internal($video_options['poster']['public_id'], $video_options['poster']); - } else { - $video_options['poster'] = cl_video_thumbnail_path($source, $video_options['poster']); - } - } - } else { - $video_options['poster'] = cl_video_thumbnail_path($source, $options); - } - - if (empty($video_options['poster'])) unset($video_options['poster']); - - - $html = ''; - return $html; - } -} diff --git a/lib/Cloudinary/PreloadedFile.php b/lib/Cloudinary/PreloadedFile.php deleted file mode 100644 index 4be08d5..0000000 --- a/lib/Cloudinary/PreloadedFile.php +++ /dev/null @@ -1,52 +0,0 @@ -resource_type = $matches[1]; - $this->type = $matches[2]; - $this->version = $matches[3]; - $this->filename = $matches[4]; - $this->signature = $matches[5]; - $public_id_and_format = $this->split_format($this->filename); - $this->public_id = $public_id_and_format[0]; - $this->format = $public_id_and_format[1]; - } else { - throw new \InvalidArgumentException("Invalid preloaded file info"); - } - } - - public function is_valid() { - $public_id = $this->resource_type == "raw" ? $this->filename : $this->public_id; - $expected_signature = \Cloudinary::api_sign_request(array("public_id" => $public_id, "version" => $this->version), \Cloudinary::config_get("api_secret")); - return $this->signature == $expected_signature; - } - - protected function split_format($identifier) { - $last_dot = strrpos($identifier, "."); - - if ($last_dot === false) { - return array($identifier, NULL); - } - $public_id = substr($identifier, 0, $last_dot); - $format = substr($identifier, $last_dot+1); - return array($public_id, $format); - } - - public function identifier() { - return "v" . $this->version . "/" . $this->filename; - } - - public function extended_identifier() { - return $this->resource_type . "/" . $this->type . "/" . $this->identifier(); - } - - public function __toString() { - return $this->resource_type . "/" . $this->type . "/v" . $this->version . "/" . $this->filename . "#" . $this->signature; - } - } -} diff --git a/lib/Cloudinary/Search.php b/lib/Cloudinary/Search.php deleted file mode 100644 index dd419d8..0000000 --- a/lib/Cloudinary/Search.php +++ /dev/null @@ -1,65 +0,0 @@ -query_hash = array( - 'sort_by' => array(), - 'aggregate' => array(), - 'with_field' => array() - ); - } - - public function expression($value) { - $this->query_hash['expression'] = $value; - return $this; - } - - public function max_results($value) { - $this->query_hash['max_results'] = $value; - return $this; - } - - public function next_cursor($value) { - $this->query_hash['next_cursor'] = $value; - return $this; - } - - public function sort_by($field_name, $dir = 'desc') { - array_push($this->query_hash['sort_by'], array($field_name => $dir)); - return $this; - } - - public function aggregate($value) { - array_push($this->query_hash['aggregate'], $value); - return $this; - } - - public function with_field($value) { - array_push($this->query_hash['with_field'], $value); - return $this; - } - - public function as_array() { - return array_filter($this->query_hash, function($value) { - return ((is_array($value) && !empty($value)) || ($value != NULL)); - }); - } - - public function execute($options = array()) { - $api = new Api(); - $uri = array('resources/search'); - $options = array_merge($options, array('content_type' => 'application/json')); - $method = 'post'; - return $api->call_api( $method, $uri, $this->as_array(), $options); - } - -} - -} - -?> \ No newline at end of file diff --git a/lib/CloudinaryExtension/CloudinaryImageProvider.php b/lib/CloudinaryExtension/CloudinaryImageProvider.php index f932f71..37f2cbe 100644 --- a/lib/CloudinaryExtension/CloudinaryImageProvider.php +++ b/lib/CloudinaryExtension/CloudinaryImageProvider.php @@ -61,7 +61,7 @@ public function upload(Image $image) public function retrieveTransformed(Image $image, Transformation $transformation) { - $imagePath = \cloudinary_url($image->getId(), [ + $imagePath = \cloudinary_url(ltrim($image->getId(), '/'), [ 'transformation' => $transformation->build(), 'secure' => true, 'sign_url' => $this->configuration->getUseSignedUrls() diff --git a/lib/CloudinaryExtension/composer.json b/lib/CloudinaryExtension/composer.json new file mode 100644 index 0000000..22cea26 --- /dev/null +++ b/lib/CloudinaryExtension/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "cloudinary/cloudinary_php": "^1.16" + } +} diff --git a/lib/CloudinaryExtension/composer.lock b/lib/CloudinaryExtension/composer.lock new file mode 100644 index 0000000..6bc57d6 --- /dev/null +++ b/lib/CloudinaryExtension/composer.lock @@ -0,0 +1,70 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "3265de913ba9de963f729a4f0be09a73", + "packages": [ + { + "name": "cloudinary/cloudinary_php", + "version": "1.16.0", + "source": { + "type": "git", + "url": "https://github.com/cloudinary/cloudinary_php.git", + "reference": "e33619e48ea8fa0350c007d640777091bfa4fdbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cloudinary/cloudinary_php/zipball/e33619e48ea8fa0350c007d640777091bfa4fdbc", + "reference": "e33619e48ea8fa0350c007d640777091bfa4fdbc", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*" + }, + "type": "library", + "autoload": { + "classmap": [ + "src" + ], + "files": [ + "src/Helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cloudinary", + "homepage": "https://github.com/cloudinary/cloudinary_php/graphs/contributors" + } + ], + "description": "Cloudinary PHP SDK", + "homepage": "https://github.com/cloudinary/cloudinary_php", + "keywords": [ + "cdn", + "cloud", + "cloudinary", + "image management", + "sdk" + ], + "time": "2019-11-28T15:57:33+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/lib/CloudinaryExtension/vendor/autoload.php b/lib/CloudinaryExtension/vendor/autoload.php new file mode 100644 index 0000000..b287c9f --- /dev/null +++ b/lib/CloudinaryExtension/vendor/autoload.php @@ -0,0 +1,7 @@ + + deny from all + diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/.travis.yml b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/.travis.yml new file mode 100644 index 0000000..287e1c7 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/.travis.yml @@ -0,0 +1,16 @@ +language: php +dist: trusty +php: + - '7.3' + - '7.2' + - '7.1' + - '7.0' + - '5.6' + - '5.5' + - '5.4' + +script: vendor/bin/phpunit +install: composer install +branches: + except: + - staging-test diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/CHANGELOG.md b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/CHANGELOG.md new file mode 100644 index 0000000..0d8221f --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/CHANGELOG.md @@ -0,0 +1,516 @@ + +1.16.0 / 2019-11-28 +=================== + +New functionality and features +------------------------------ + + * Add `update_metadata` Upload API. + * Add `metadata` parameter to `upload` and `explicit` Upload APIs. + +1.15.1 / 2019-10-30 +=================== + + * Fix `Undefined index` warning in `cl_upload_url` + * Fix PHP7.4 deprecation warnings + * Detect data URLs with suffix in mime type + * Fix samples when using without composer + +1.15.0 / 2019-10-02 +=================== + +New functionality and features +------------------------------ + + * Add `create_folder` admin API + * Add `max_results` and `next_cursor` for folders APIs + * Add `live` parameter to `create_upload_preset` and `update_upload_preset` APIs + * Add `cinemagraph_analysis` parameter + * Add `duration` and `initial_duration` predefined variables + * Add `pow` transformation operator + * Allow generating archive with multiple resource types + +Other Changes +------------- + + * Fix transformations API call + * Fix `AuthToken` `UNSAFE` invalid regex + * Fix `normalize_expression` to ignore predefined variables + * Validate `CLOUDINARY_URL` scheme + * Fix travis build + +1.14.0 / 2019-05-13 +=================== + +New functionality and features +------------------------------ + * Add `delete_folder` admin API + * Add `filename` parameter to `upload` API + * Add `derived_next_cursor` to `resource` admin API + * Add `SignatureVerifier` class + * Add `force_version` option to delivery urls + +Other Changes +------------- + * Fix notice message in `generate_sprite` method + * Fix acl and url escaping in auth_token generation + * Ignore URL in AuthToken generation if ACL is provided + * Fix base64 regex validation template + * Add support of PHP 7.3 + * Improve error handling when trying to upload non-existing file. + * Add `test_secure_distribution_from_config` unit test + * Remove redundant quotes in `update_version.sh` + +1.13.0 / 2018-12-16 +=================== + +New functionality and features +------------------------------ + + * Add custom `pre` function support + * Add `fps` video transformation parameter + * Add `keyframe_interval` video transformation parameter + * Add `quality_analysis` upload parameter + +Other Changes +------------- + + * Fix file extension in `upload_large` + * Fix for Uploader.php to prevent uploads failing + * Fix missing padding in `base64url_encode` + * Mock upload presets tests + * Fix `test_detection` unit test + * Remove secure variables from travis.yml + +1.12.0 / 2018-10-11 +=================== + +New functionality and features +------------------------------ + * Add support of custom codecs in video tag + * Add Google Cloud Storage protocol support in upload + +Other Changes +------------- + * Add `update_version.sh` tool + * Rename `custom_action` to `custom_function` + +1.11.1 / 2018-09-12 +=================== + + * Update version number in files + +1.11.0 / 2018-09-07 +=================== + +New functionality and features +------------------------------ + * Add responsive breakpoints cache + * Add `cl_picture_tag` and `cl_source_tag` helpers + * Add support for web assembly and lambda functions in transformations. + * Add `font_antialiasing` and `font_hinting` text style parameters + * Add `cl_client_hints_meta_tag` helper + * Add support of `named` parameter in list transformations API + * Add support of `auto` value for `start_offset` transformation parameter (#123) + * Add `notification_url` param to the `update` method + * Add `format` parameter to responsive breakpoints settings + * Add legacy autoloader + +Other Changes +------------- + * Improve PSR-2 compliance + PSR-4 autoload + documentation update + * Update `radius` transformation parameter + * Add `base64url_encode` internal helper + * Fix base64 encoding in urls. + * Use `X-Unique-Upload-Id` header in `upload_large` + * Fix sample project includes. + +1.10.0 / 2018-05-10 +=================== + +New functionality and features +------------------------------ + + * Add `srcset` and `sizes` attributes to the `img` tag (#117) + * Support special characters in public IDs, tags, etc in Admin API calls + * Add php version to user agent (#114) + +Other Changes +------------- + * Fix URL-encode parts of api-uri + * Add `test_url_encoding` unit test + * Fix `test_transformation_cursor_results` unit test + * Fix `test_raw_conversion` unit test + * Fix streaming profiles cleanup after unit tests + * Add PHPDoc to Cloudinary\Api and Exceptions + * Update PHP version requirement + +1.9.0 / 2018-03-12 +================== + +New functionality and features +------------------------------ + + * Add `delete_derived_by_transformation` API + * Add `remove_all_tags` to `Uploader` + * Add `resources_by_context` to `Api` + * Add `access_control` parameter to uploader `upload` and api `update` + * Support remote URLs in upload_large API + * Add `transformations` parameter to delete_resources APIs + * Support `quality_override` param for `update` and `explicit` api + * Adding Streaming Profile to transformation. + * Add URL suffix support for image/authenticated, video/upload + * Remove restriction of URL suffix in shared CDN + * Support string $public_ids parameter in `delete_derived_by_transformation` + * Support `0` and `0.0` in `norm_range_value` function. Fixes #64 (#97) + +Other Changes +------------- + + * Improve PSR-2 compliance (#101) + * Add `next_cursor` test of `transformation()` API + * Add `encode_array_to_json` + * Fix encoding of attributes and url in html tags. Fixes #87 + * Add PHP versions to TravisCI setup + * Add a test of streaming_profile parameter + * Fix Categorization test + * Add `UNIQUE_TEST_ID` to test helper + * Remove `test_auto_tagging` unit test (#95) + * Update Readme.md for setup with composer + * Remove unreachable code. Fixes #66 + +1.8.0 / 2017-05-03 +================== + +New functionality and features +------------------------------ + + * Advanced search API + * Add `async` parameter to upload parameters. + +Other Changes +------------- + + * Update tests to use `TestHelper` + * Add compatibility for newer PHPUnit versions + +1.7.2 / 2017-04-03 +================== + + * Add update ocr parameters test + * Merge pull request #71 from jtabet/fix-floats-issue + * Added a number_format on float values in the transformation string + * Add ocr parameters tests + * Fix variables order. Add variables order tests. + +1.7.1 / 2017-03-13 +================== + + * Update phpunit to 5.7.* + * Update travis.yml to test 5.6 and 7.0 (matching phpunit) + * Don't normalize negative numbers. Fixed #68. + +1.7.0 / 2017-03-09 +================== + +New functionality and features +------------------------------ + + * User defined variables + * Add `async` parameter to upload params (#65) + * Add `fetch` prefix to overlay path + * Support fetch overlay underlay + +Other Changes +------------- + + * Rename items and add missing variables. + +1.6.2 / 2017-02-23 +================== + + * Add URL authentication. + * Rename `auth_token`. + * Support nested values in `CLOUDINARY_URL` + * Fix archive test. + * Add a test for `build_eager`. + +1.6.1 / 2017-02-16 +================== + + * Allow 'invalidate' param in 'delete_transformation' + * Upgrade Travis test from 7.0 to 7.1 + * Merge pull request #61 from dragosprotung/patch-1 + * Merge pull request #63 from cloudinary/support-invalidate-in-delete-transformation + * Deleted stub file + +1.6.0 / 2017-01-30 +================== + +New functionality and features +------------------------------ + + * Add Akamai token generator + +Other Changes +------------- + + * Revert using VERSION to set USER_AGENT. Fixes #58. + * Fix USER_AGENT version. + +1.5.0 / 2017-01-19 +================== + +New functionality and features +------------------------------ + + * New `add_context` & `remove_all_context` API + * support suffix url for private images + * Escape ‘|' and ‘=‘ characters in context values + * Support ‘iw’ and ‘ih’ transformation parameters for indicating initial width or height + * Support `to_type` parameter in `rename` + +Other Changes +------------- + + * Fix folder listing test + * Add test for {effect: art:incognito} + * expending retrieved list of transformation to allow test to pass properly + * Add test case for 'to_type' + fix face_coordintes exceeding image boundaries + * Fix typo in the archive `expires_at` parameter + * Remove `$name` from call to `list_streaming_profiles` + +1.4.2 / 2016-10-28 +================== + +New functionality and features +------------------------------ + + * Add streaming profiles API + * Merge pull request #40 from sergey-safonov/feature/config-connection-timeout + * Allow specify connection timeout in config + +1.4.1 / 2016-08-14 +================== + +New functionality and features +------------------------------ + + * Add `allow_missing` parameter to the archive api + * Add `skip_transformation_name` parameter to `create_archive`. + * Add `expire_at` parameter to `create_archive`. + * Add `transformation` parameter to `delete_resources`. + * Add original height and width test. + * Allow `cloud_name` to be specified in options array + * Add TravisCI configuration + * Add badges to README.md + * Add license file + * Update sample project: use cdnjs instead of locally stored JS files and bootstrap with `cloudinary_fileupload()`. + +Other Changes +------------- + + * Merge pull request #38 from RobinMalfait/patch-1 + * Merge pull request #37 from Welkio/master + * Merge pull request #41 from DacotahHarvey + * Fix Zip tests. + * Add default message to assertPost, assertGet, assertPut, assertDelete. Add optional message to assertUrl. + * Add assert helper methods. + * Add test for `gravity: auto` parameter. + * Use eager transformation in timeout test. + * Remove `overwrite` test. + * Mock `eager` test. + * Use random number for test tag. + * Add `url_prefix` to the tests. + * Mock restore tests. + * Mock upload_presets tests. + * Mock start_at test + * Separare `mock` to `apiMock` and `uploadMock`. Use random public_ids in API tests. + * Update README.md + +1.4.0 / 2016-06-22 +================== + +New functionality and features +------------------------------ + + * New configuration parameter `:client_hints` + * Enhanced auto `width` values + * Enhanced `quality` values + +Other Changes +------------- + + * Disable explicit test + +1.3.2 / 2016-06-02 +================== + + * Add `next_cursor` to `Api->transformation()`. + * Remove empty parameters from `update()` calls + * Add tests + * Add TestHelper.php. Create new `Curl` class. + * Use constants in tests + * Use comma in delete resources test + +1.3.1 / 2016-03-22 +================== + +New functionality and features +------------------------------ + + * Conditional Transformations + +Other Changes +------------- + + * Fix categorization test + * Use original file name as `public_id` for server side upload (sample project). + * Remove support for `exclusive` in `add_tag` + * Pass parameters in body unless it's a `get` call + * Support PHP versions before 5.4 + * Use `isset` instead of `!= NULL` + +1.3.0 / 2016-01-28 +================== + + * New ZIP generation API. + * Support responsive_breakpoints upload/explicit parameter. + * Support line_spacing text layer parameter. + * Support array parameters in Uploader. + * Fix layer processsing + * Implement parametrized test for layers + * Better escaping for , and / in text layer + +1.2.0 / 2015-11-01 +================== + + * Escape / in overlays + * Support crc32 on 32-bit systems + * Support upload_mappings API + * Support Backup restoration API + * Support easy overlay/underlay construction + * Add script to update and commit new version + * Add invalidate parameter to rename + +1.1.4 / 2015-08-23 +================== + + * Support passing array arguments in POST body for Uploader + * Add test for #33 - huge id list in `add_tag` api. + +1.1.3 / 2015-08-19 +================== + + * Add aspect_ratio + * Add `context` and `invalidate` to the explicit API parameters. + * Fix timeout test and make test compatible with PHP 5.3 + * Replace CURLOPT_TIMEOUT_MS with CURLOPT_TIMEOUT as it is not supported before cURL 7.16.2. + * Added comments specifying curl option version requirements. + +1.1.2 / 2015-07-27 +================== + + * Fix eager ignoring format + +1.1.1 / 2015-06-2 +=================== + + + * new format and method for USER_AGENT + * support adding information to the USER_AGENT + * solve bad URLs created with secure_cdn_subdomain. Resolves #28 + +1.1.0 / 2015-04-7 +=================== + + * support video tag generation and url helpers + * support video transformation parameters: audio_codec, audio_frequency, bit_rate, video_sampling, duration, end_offset, start_offset, video_codec + * support zoom transformation parameter + * support ftp url + * allow specifying request timeout + * enable eager_async and eager_notification_url in explicit + * change upload_large's endpoint to use upload with content_range header + * support chunk_size in cl_upload_tag + +1.0.17 / 2015-02-10 +=================== + + * Add a changelog + * Add support for 'overwrite' option in upload + * Allow root path for shared CDN + +1.0.16 / 2014-12-22 +=================== + + * Support folder listing + * Secure domain sharding + * Don't sign version component + * URL suffix and root path support + * Support tags in upload large + * Make call_api public + +1.0.15 / 2014-11-2 +=================== + + * Support api_proxy parameter for setting up a proxy between the PHP client and Cloudinary + * Fixed HHVM compatibility issue + +1.0.14 / 2014-10-15 +=================== + + * Remove force SSLv3 + +1.0.13 / 2014-09-22 +=================== + + * Force SSLv3 when contacting the Cloudinary API + * Support invalidation in bulk deletion req (if enabled in your account) + +1.0.12 / 2014-08-24 +=================== + + * Support custom_coordinates is upload and update + * Support coordinates in resource details + * Support return_delete_token parameter in upload and cl_image_upload_tag + * Correctly escape parentheses + +1.0.11 / 2014-07-7 +=================== + + * Support for auto dpr, auto width and responsive width + * Support for background_removal in upload and update + +1.0.10 / 2014-04-29 +=================== + + * Remove closing PHP tags + * Support upload_presets + * Support unsigned uploads + * Support start_at for resource listing + * Support phash for upload and resource details + * Better error message in case of file not found in uploader for PHP 5.5+ + +1.0.9 / 2014-02-26 +=================== + + * Admin API update method + * Admin API listing by moderation kind and status + * Support moderation status in admin API listing + * Support moderation flag in upload + * New Upload and update API parameters: moderation, ocr, raw_conversation, categorization, detection, similarity_search and auto_tagging + * Support CLOUDINARY_URL ending with / + * Support for uploading large raw files + +1.0.8 / 2014-01-21 +=================== + + * Support overwrite upload parameter + * Support specifying face coordinates in upload API + * Support specifying context (currently alt and caption) in upload API and returning context in API + * Support specifying allowed image formats in upload API + * Support listing resources in admin API by multiple public IDs + * Send User-Agent header with client library version in API request + * Support for signed-URLs to override restricted dynamic URLs + * Move helper methods and preloaded file to separate file and fix Composer autoload + * Minor fixes diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/LICENSE.txt b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/LICENSE.txt new file mode 100644 index 0000000..e6a44f1 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/LICENSE.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Cloudinary + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/README.md b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/README.md new file mode 100644 index 0000000..8799c03 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/README.md @@ -0,0 +1,216 @@ +[![Build Status](https://travis-ci.org/cloudinary/cloudinary_php.svg)](https://travis-ci.org/cloudinary/cloudinary_php) [![license](https://img.shields.io/github/license/cloudinary/cloudinary_php.svg?maxAge=2592000)]() [![Packagist](https://img.shields.io/packagist/v/cloudinary/cloudinary_php.svg?maxAge=2592000)]() [![Packagist](https://img.shields.io/packagist/dt/cloudinary/cloudinary_php.svg?maxAge=2592000)]() + +Cloudinary +========== + +Cloudinary is a cloud service that offers a solution to a web application's entire image management pipeline. + +Easily upload images to the cloud. Automatically perform smart image resizing, cropping and conversion without installing any complex software. Integrate Facebook or Twitter profile image extraction in a snap, in any dimension and style to match your website's graphics requirements. Images are seamlessly delivered through a fast CDN, and much much more. + +Cloudinary offers comprehensive APIs and administration capabilities and is easy to integrate with any web application, existing or new. + +Cloudinary provides URL and HTTP based APIs that can be easily integrated with any Web development framework. + +For PHP, Cloudinary provides an extension for simplifying the integration even further. + +## Getting started guide +![](http://res.cloudinary.com/cloudinary/image/upload/see_more_bullet.png) **Take a look at our [Getting started guide for PHP](http://cloudinary.com/documentation/php_integration#getting_started_guide)**. + + +## CakePHP ## +Dedicated CakePHP plugin is also available. You can browse the code, installation and usage information [at the `cloudinary_cake_php` repository](https://github.com/cloudinary/cloudinary_cake_php). + +## Setup ###################################################################### + +You can install through composer with: + +`composer require cloudinary/cloudinary_php` + +Or download cloudinary_php from [here](https://github.com/cloudinary/cloudinary_php/tarball/master) + +*Note: cloudinary_php require PHP 5.4* + +## Try it right away + +Sign up for a [free account](https://cloudinary.com/users/register/free) so you can try out image transformations and seamless image delivery through CDN. + +*Note: Replace `demo` in all the following examples with your Cloudinary's `cloud name`.* + +Accessing an uploaded image with the `sample` public ID through a CDN: + + http://res.cloudinary.com/demo/image/upload/sample.jpg + +![Sample](https://cloudinary-a.akamaihd.net/demo/image/upload/w_0.4/sample.jpg "Sample") + +Generating a 150x100 version of the `sample` image and downloading it through a CDN: + + http://res.cloudinary.com/demo/image/upload/w_150,h_100,c_fill/sample.jpg + +![Sample 150x100](https://cloudinary-a.akamaihd.net/demo/image/upload/w_150,h_100,c_fill/sample.jpg "Sample 150x100") + +Converting to a 150x100 PNG with rounded corners of 20 pixels: + + http://res.cloudinary.com/demo/image/upload/w_150,h_100,c_fill,r_20/sample.png + +![Sample 150x150 Rounded PNG](https://cloudinary-a.akamaihd.net/demo/image/upload/w_150,h_100,c_fill,r_20/sample.png "Sample 150x150 Rounded PNG") + +For plenty more transformation options, see our [image transformations documentation](http://cloudinary.com/documentation/image_transformations). + +Generating a 120x90 thumbnail based on automatic face detection of the Facebook profile picture of Bill Clinton: + + http://res.cloudinary.com/demo/image/facebook/c_thumb,g_face,h_90,w_120/billclinton.jpg + +![Facebook 90x120](https://cloudinary-a.akamaihd.net/demo/image/facebook/c_thumb,g_face,h_90,w_120/billclinton.jpg "Facebook 90x200") + +For more details, see our documentation for embedding [Facebook](http://cloudinary.com/documentation/facebook_profile_pictures) and [Twitter](http://cloudinary.com/documentation/twitter_profile_pictures) profile pictures. + +### Samples +You can find our simple and ready-to-use samples projects, along with documentations in the [samples folder](https://github.com/cloudinary/cloudinary_php/tree/master/samples). Please consult with the [README file](https://github.com/cloudinary/cloudinary_php/blob/master/samples/README.md), for usage and explanations. + +## Usage + +### Configuration + +Each request for building a URL of a remote cloud resource must have the `cloud_name` parameter set. +Each request to our secure APIs (e.g., image uploads, eager sprite generation) must have the `api_key` and `api_secret` parameters set. See [API, URLs and access identifiers](http://cloudinary.com/documentation/api_and_access_identifiers) for more details. + +Setting the `cloud_name`, `api_key` and `api_secret` parameters can be done either directly in each call to a Cloudinary method, by calling the Cloudinary::config(), or by using the CLOUDINARY_URL environment variable. + +### Embedding and transforming images + +Any image uploaded to Cloudinary can be transformed and embedded using powerful view helper methods: + +The following example generates the url for accessing an uploaded `sample` image while transforming it to fill a 100x150 rectangle: + + cloudinary_url("sample.jpg", array("width" => 100, "height" => 150, "crop" => "fill")) + +Another example, emedding a smaller version of an uploaded image while generating a 90x90 face detection based thumbnail: + + cloudinary_url("woman.jpg", array("width" => 90, "height" => 90, "crop" => "thumb", "gravity" => "face")) + +You can provide either a Facebook name or a numeric ID of a Facebook profile or a fan page. + +Embedding a Facebook profile to match your graphic design is very simple: + + cloudinary_url("billclinton.jpg", array("width" => 90, "height" => 130, "type" => "facebook", "crop" => "fill", "gravity" => "north_west")) + +Same goes for Twitter: + + cloudinary_url("billclinton.jpg", array("type" => "twitter_name")) + +![](http://res.cloudinary.com/cloudinary/image/upload/see_more_bullet.png) **See [our documentation](http://cloudinary.com/documentation/php_image_manipulation) for more information about displaying and transforming images in PHP**. + + + +### Upload + +Assuming you have your Cloudinary configuration parameters defined (`cloud_name`, `api_key`, `api_secret`), uploading to Cloudinary is very simple. + +The following example uploads a local JPG to the cloud: + + \Cloudinary\Uploader::upload("my_picture.jpg") + +The uploaded image is assigned a randomly generated public ID. The image is immediately available for download through a CDN: + + cloudinary_url("abcfrmo8zul1mafopawefg.jpg") + + http://res.cloudinary.com/demo/image/upload/abcfrmo8zul1mafopawefg.jpg + +You can also specify your own public ID: + + \Cloudinary\Uploader::upload("http://www.example.com/image.jpg", array("public_id" => 'sample_remote')) + + cloudinary_url("sample_remote.jpg") + + http://res.cloudinary.com/demo/image/upload/sample_remote.jpg + + +![](http://res.cloudinary.com/cloudinary/image/upload/see_more_bullet.png) **See [our documentation](http://cloudinary.com/documentation/php_image_upload) for plenty more options of uploading to the cloud from your PHP code**. + + +### cl\_image\_tag + +Returns an html image tag pointing to Cloudinary. + +Usage: + + "png", "width"=>100, "height"=>100, "crop"=>"fill") ?> + + # + + + +### cl\_image\_upload\_tag + +Returns an html input field for direct image upload, to be used in conjunction with [cloudinary\_js package](https://github.com/cloudinary/cloudinary_js/). It integrates [jQuery-File-Upload widget](https://github.com/blueimp/jQuery-File-Upload) and provides all the necessary parameters for a direct upload. +You may see a sample usage of this feature in the PhotoAlbum sample included in this project. + +Usage: + + cl_image_upload_tag(post-upload-field-name, upload-options-array) + +Parameters: + + - `post-upload-field-name` - A name of a field in the form to be updated with the uploaded file data. + If no such field exists a new hidden field will be creates. + The value format is `#`. + If the `cl_image_upload_tag` is not within an html form, this argument is ignored. + + - `upload-options-array` - upload options same as in Upload section above, with: + - html - an associative array of html attributes for the upload field + +![](http://res.cloudinary.com/cloudinary/image/upload/see_more_bullet.png) **See [our documentation](http://cloudinary.com/documentation/php_image_upload#direct_uploading_from_the_browser) for plenty more options of uploading directly from the browser**. + + +### cl\_form\_tag + +The following function returns an html form that can be used to upload the file directly to Cloudinary. The result is a redirect to the supplied callback_url. + + cl_form_tag(callback, array(...)) + +Optional parameters: + + public_id - The name of the uploaded file in Cloudinary + form - html attributes to be added to the form tag + Any other parameter that can be passed to \Cloudinary\Uploader::upload + +## Development + +### Testing + +To run the PHPUnit test suite you must first set the environment variable containing your Cloudinary URL. This can be obtained via Cloudinary's Management Console. + + export CLOUDINARY_URL=cloudinary://123456789012345:abcdeghijklmnopqrstuvwxyz12@n07t21i7 + +Next you can run your the PHPUnit suite from the root of this library: + + phpunit tests/* + +## Additional resources ########################################################## + +Additional resources are available at: + +* [Website](http://cloudinary.com) +* [Knowledge Base](http://support.cloudinary.com/forums) +* [Documentation](http://cloudinary.com/documentation) +* [Documentation for PHP integration](http://cloudinary.com/documentation/php_integration) +* [PHP image upload documentation](http://cloudinary.com/documentation/php_image_upload) +* [PHP image manipulation documentation](http://cloudinary.com/documentation/php_image_manipulation) +* [Image transformations documentation](http://cloudinary.com/documentation/image_transformations) + +## Support + +You can [open an issue through GitHub](https://github.com/cloudinary/cloudinary_php/issues). + +Contact us [http://cloudinary.com/contact](http://cloudinary.com/contact) + +Stay tuned for updates, tips and tutorials: [Blog](http://cloudinary.com/blog), [Twitter](https://twitter.com/cloudinary), [Facebook](http://www.facebook.com/Cloudinary). + +## Join the Community ########################################################## + +Impact the product, hear updates, test drive new features and more! Join [here](https://www.facebook.com/groups/CloudinaryCommunity). + +## License ####################################################################### + +Released under the MIT license. + diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/autoload.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/autoload.php new file mode 100644 index 0000000..d870dfc --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/autoload.php @@ -0,0 +1,39 @@ +=5.4.0", + "ext-curl": "*", + "ext-json": "*" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*" + }, + "support": { + "email": "info@cloudinary.com", + "issues": "https://github.com/cloudinary/cloudinary_php/issues" + }, + "autoload": { + "classmap": ["src"], + "files": ["src/Helpers.php"] + }, + "autoload-dev": { + "psr-4": { "Cloudinary\\Test\\": "tests/" }, + "files": ["tests/TestHelper.php"] + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/composer.lock b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/composer.lock new file mode 100644 index 0000000..d93459f --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/composer.lock @@ -0,0 +1,985 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "ad16b6d4a90a4e2b380e6ddb341cee61", + "packages": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.4,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14T21:17:01+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e6a969a640b00d8daa3c66518b0405fb41ae0c4b", + "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2016-01-25T08:17:30+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.7.5", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/dfd6be44111a7c41c2e884a336cc4f461b3b2401", + "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2018-02-19T10:16:54+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-10-06T15:47:00+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-12-04T08:55:13+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.36", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.2.2", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2017-06-21T08:07:12+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2015-10-02T06:51:40+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-08-18T05:49:44+00:00" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-06-17T09:04:28+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-10-03T07:41:43+00:00" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21T13:59:46+00:00" + }, + { + "name": "symfony/yaml", + "version": "v2.8.34", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "be720fcfae4614df204190d57795351059946a77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/be720fcfae4614df204190d57795351059946a77", + "reference": "be720fcfae4614df204190d57795351059946a77", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2018-01-03T07:36:31+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.0", + "ext-curl": "*", + "ext-json": "*" + }, + "platform-dev": [] +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/phpcs.xml b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/phpcs.xml new file mode 100644 index 0000000..33831f6 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/phpcs.xml @@ -0,0 +1,23 @@ + + + cloudinary_php coding standard + + + + + + + + + + + + + + + + + + src + tests + diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/phpunit.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/phpunit.php new file mode 100644 index 0000000..49ef939 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/phpunit.php @@ -0,0 +1,8 @@ + + + + + + + + tests + + + + + + diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/.gitignore b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/.gitignore new file mode 100644 index 0000000..edd8de6 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/.gitignore @@ -0,0 +1 @@ +settings.php diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/autoloader.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/autoloader.php new file mode 100644 index 0000000..4dfe2cb --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/autoloader.php @@ -0,0 +1,9 @@ + + + + + + + + + + diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/lib/rb-license.txt b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/lib/rb-license.txt new file mode 100644 index 0000000..a0b21de --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/lib/rb-license.txt @@ -0,0 +1,329 @@ +/* + + .______. +_______ ____ __| _/\_ |__ ____ _____ ____ +\_ __ \_/ __ \ / __ | | __ \_/ __ \\__ \ / \ + | | \/\ ___// /_/ | | \_\ \ ___/ / __ \| | \ + |__| \___ >____ | |___ /\___ >____ /___| / + \/ \/ \/ \/ \/ \/ + + + +RedBean Database Objects - +Written by Gabor de Mooij (c) copyright 2009-2012 + +RedBean is DUAL Licensed BSD and GPLv2. You may choose the license that fits +best for your project. + + +BSD/GPLv2 License + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of RedBeanPHP nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY GABOR DE MOOIJ ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL GABOR DE MOOIJ BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +RedBeanPHP is Written by Gabor de Mooij (G.J.G.T de Mooij) Copyright (c) 2011. + + +GPLv2 LICENSE + + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +*/ diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/lib/rb.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/lib/rb.php new file mode 100644 index 0000000..85dc014 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/lib/rb.php @@ -0,0 +1,9515 @@ +____ | |___ /\___ >____ /___| / + \/ \/ \/ \/ \/ \/ + + + +RedBean Database Objects - +Written by Gabor de Mooij (c) copyright 2009-2012 + +RedBean is DUAL Licensed BSD and GPLv2. You may choose the license that fits +best for your project. + + +BSD/GPLv2 License + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of RedBeanPHP nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY GABOR DE MOOIJ ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL GABOR DE MOOIJ BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +RedBeanPHP is Written by Gabor de Mooij (G.J.G.T de Mooij) Copyright (c) 2011. + + +GPLv2 LICENSE + + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +*/ + + +/** + * Interface for database drivers + * + * @file RedBean/Driver.php + * @desc Describes the API for database classes + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * The Driver API conforms to the ADODB pseudo standard + * for database drivers. + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +interface RedBean_Driver { + /** + * Runs a query and fetches results as a multi dimensional array. + * + * @param string $sql SQL to be executed + * + * @return array $results result + */ + public function GetAll( $sql, $aValues=array() ); + + /** + * Runs a query and fetches results as a column. + * + * @param string $sql SQL Code to execute + * + * @return array $results Resultset + */ + public function GetCol( $sql, $aValues=array() ); + + /** + * Runs a query an returns results as a single cell. + * + * @param string $sql SQL to execute + * + * @return mixed $cellvalue result cell + */ + public function GetCell( $sql, $aValues=array() ); + + /** + * Runs a query and returns a flat array containing the values of + * one row. + * + * @param string $sql SQL to execute + * + * @return array $row result row + */ + public function GetRow( $sql, $aValues=array() ); + + /** + * Executes SQL code and allows key-value binding. + * This function allows you to provide an array with values to bind + * to query parameters. For instance you can bind values to question + * marks in the query. Each value in the array corresponds to the + * question mark in the query that matches the position of the value in the + * array. You can also bind values using explicit keys, for instance + * array(":key"=>123) will bind the integer 123 to the key :key in the + * SQL. This method has no return value. + * + * @param string $sql SQL Code to execute + * @param array $aValues Values to bind to SQL query + * + * @return void + */ + public function Execute( $sql, $aValues=array() ); + + /** + * Escapes a string for use in SQL using the currently selected + * driver driver. + * + * @param string $string string to be escaped + * + * @return string $string escaped string + */ + public function Escape( $str ); + + /** + * Returns the latest insert ID if driver does support this + * feature. + * + * @return integer $id primary key ID + */ + public function GetInsertID(); + + + /** + * Returns the number of rows affected by the most recent query + * if the currently selected driver driver supports this feature. + * + * @return integer $numOfRows number of rows affected + */ + public function Affected_Rows(); + + /** + * Toggles debug mode. In debug mode the driver will print all + * SQL to the screen together with some information about the + * results. All SQL code that passes through the driver will be + * passes on to the screen for inspection. + * This method has no return value. + * + * @param boolean $trueFalse turn on/off + * + * @return void + */ + public function setDebugMode( $tf ); + + /** + * Starts a transaction. + * This method is part of the transaction mechanism of + * RedBeanPHP. All queries in a transaction are executed together. + * In case of an error all commands will be rolled back so none of the + * SQL in the transaction will affect the DB. Using transactions is + * considered best practice. + * This method has no return value. + * + * @return void + */ + public function CommitTrans(); + + /** + * Commits a transaction. + * This method is part of the transaction mechanism of + * RedBeanPHP. All queries in a transaction are executed together. + * In case of an error all commands will be rolled back so none of the + * SQL in the transaction will affect the DB. Using transactions is + * considered best practice. + * This method has no return value. + * + * @return void + */ + public function StartTrans(); + + /** + * Rolls back a transaction. + * This method is part of the transaction mechanism of + * RedBeanPHP. All queries in a transaction are executed together. + * In case of an error all commands will be rolled back so none of the + * SQL in the transaction will affect the DB. Using transactions is + * considered best practice. + * This method has no return value. + * + * @return void + */ + public function FailTrans(); +} + + +/** + * PDO Driver + * @file RedBean/PDO.php + * @desc PDO Driver + * @author Gabor de Mooij and the RedBeanPHP Community, Desfrenes + * @license BSD/GPLv2 + * + * This Driver implements the RedBean Driver API + * + * (c) copyright Desfrenes & Gabor de Mooij and the RedBeanPHP community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + * + */ +class RedBean_Driver_PDO implements RedBean_Driver { + + /** + * Contains database DSN for connecting to database. + * @var string + */ + protected $dsn; + + /** + * Whether we are in debugging mode or not. + * @var boolean + */ + protected $debug = false; + + /** + * Holds an instance of Logger implementation. + * @var RedBean_Logger + */ + protected $logger = NULL; + + /** + * Holds the PDO instance. + * @var PDO + */ + protected $pdo; + + /** + * Holds integer number of affected rows from latest query + * if driver supports this feature. + * @var integer + */ + protected $affected_rows; + + /** + * Holds result resource. + * @var integer + */ + protected $rs; + + + /** + * Contains arbitrary connection data. + * @var array + */ + protected $connectInfo = array(); + + + /** + * Whether you want to use classic String Only binding - + * backward compatibility. + * @var bool + */ + public $flagUseStringOnlyBinding = false; + + /** + * Whether we are currently connected or not. + * This flag is being used to delay the connection until necessary. + * Delaying connections is a good practice to speed up scripts that + * don't need database connectivity but for some reason want to + * init RedbeanPHP. + * @var boolean + */ + protected $isConnected = false; + + /** + * Constructor. You may either specify dsn, user and password or + * just give an existing PDO connection. + * Examples: + * $driver = new RedBean_Driver_PDO($dsn, $user, $password); + * $driver = new RedBean_Driver_PDO($existingConnection); + * + * @param string|PDO $dsn database connection string + * @param string $user optional, usename to sign in + * @param string $pass optional, password for connection login + * + * @return void + */ + public function __construct($dsn, $user = null, $pass = null) { + if ($dsn instanceof PDO) { + $this->pdo = $dsn; + $this->isConnected = true; + $this->pdo->setAttribute(1002, 'SET NAMES utf8'); + $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + // make sure that the dsn at least contains the type + $this->dsn = $this->getDatabaseType(); + } else { + $this->dsn = $dsn; + $this->connectInfo = array( 'pass'=>$pass, 'user'=>$user ); + } + } + + /** + * Establishes a connection to the database using PHP PDO + * functionality. If a connection has already been established this + * method will simply return directly. This method also turns on + * UTF8 for the database and PDO-ERRMODE-EXCEPTION as well as + * PDO-FETCH-ASSOC. + * + * @return void + */ + public function connect() { + if ($this->isConnected) return; + $user = $this->connectInfo['user']; + $pass = $this->connectInfo['pass']; + //PDO::MYSQL_ATTR_INIT_COMMAND + $this->pdo = new PDO( + $this->dsn, + $user, + $pass, + array(1002 => 'SET NAMES utf8', + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + + ) + ); + $this->pdo->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); + $this->isConnected = true; + } + + /** + * Binds parameters. This method binds parameters to a PDOStatement for + * Query Execution. This method binds parameters as NULL, INTEGER or STRING + * and supports both named keys and question mark keys. + * + * @param PDOStatement $s PDO Statement instance + * @param array $aValues values that need to get bound to the statement + * + * @return void + */ + protected function bindParams($s,$aValues) { + foreach($aValues as $key=>&$value) { + if (is_integer($key)) { + + if (is_null($value)){ + $s->bindValue($key+1,null,PDO::PARAM_NULL); + } + elseif (!$this->flagUseStringOnlyBinding && RedBean_QueryWriter_AQueryWriter::canBeTreatedAsInt($value) && $value < 2147483648) { + $s->bindParam($key+1,$value,PDO::PARAM_INT); + } + else { + $s->bindParam($key+1,$value,PDO::PARAM_STR); + } + } + else { + if (is_null($value)){ + $s->bindValue($key,null,PDO::PARAM_NULL); + } + elseif (!$this->flagUseStringOnlyBinding && RedBean_QueryWriter_AQueryWriter::canBeTreatedAsInt($value) && $value < 2147483648) { + $s->bindParam($key,$value,PDO::PARAM_INT); + } + else { + $s->bindParam($key,$value,PDO::PARAM_STR); + } + } + + } + } + + /** + * Runs a query. Internal function, available for subclasses. This method + * runs the actual SQL query and binds a list of parameters to the query. + * slots. The result of the query will be stored in the protected property + * $rs (always array). The number of rows affected (result of rowcount, if supported by database) + * is stored in protected property $affected_rows. If the debug flag is set + * this function will send debugging output to screen buffer. + * + * @throws RedBean_Exception_SQL + * + * @param string $sql the SQL string to be send to database server + * @param array $aValues the values that need to get bound to the query slots + */ + protected function runQuery($sql,$aValues) { + $this->connect(); + if ($this->debug && $this->logger) { + $this->logger->log($sql, $aValues); + } + try { + if (strpos('pgsql',$this->dsn)===0) { + $s = $this->pdo->prepare($sql, array(PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT => true)); + } + else { + $s = $this->pdo->prepare($sql); + } + $this->bindParams( $s, $aValues ); + $s->execute(); + $this->affected_rows = $s->rowCount(); + if ($s->columnCount()) { + $this->rs = $s->fetchAll(); + if ($this->debug && $this->logger) $this->logger->log('resultset: ' . count($this->rs) . ' rows'); + } + else { + $this->rs = array(); + } + }catch(PDOException $e) { + //Unfortunately the code field is supposed to be int by default (php) + //So we need a property to convey the SQL State code. + $err = $e->getMessage(); + if ($this->debug && $this->logger) $this->logger->log('An error occurred: '.$err); + $x = new RedBean_Exception_SQL( $err, 0); + $x->setSQLState( $e->getCode() ); + throw $x; + } + } + + + /** + * Runs a query and fetches results as a multi dimensional array. + * + * @param string $sql SQL to be executed + * + * @return array $results result + */ + public function GetAll( $sql, $aValues=array() ) { + $this->runQuery($sql,$aValues); + return $this->rs; + } + + /** + * Runs a query and fetches results as a column. + * + * @param string $sql SQL Code to execute + * + * @return array $results Resultset + */ + public function GetCol($sql, $aValues=array()) { + $rows = $this->GetAll($sql,$aValues); + $cols = array(); + if ($rows && is_array($rows) && count($rows)>0) { + foreach ($rows as $row) { + $cols[] = array_shift($row); + } + } + return $cols; + } + + /** + * Runs a query an returns results as a single cell. + * + * @param string $sql SQL to execute + * + * @return mixed $cellvalue result cell + */ + public function GetCell($sql, $aValues=array()) { + $arr = $this->GetAll($sql,$aValues); + $row1 = array_shift($arr); + $col1 = array_shift($row1); + return $col1; + } + + /** + * Runs a query and returns a flat array containing the values of + * one row. + * + * @param string $sql SQL to execute + * + * @return array $row result row + */ + public function GetRow($sql, $aValues=array()) { + $arr = $this->GetAll($sql, $aValues); + return array_shift($arr); + } + + + + /** + * Executes SQL code and allows key-value binding. + * This function allows you to provide an array with values to bind + * to query parameters. For instance you can bind values to question + * marks in the query. Each value in the array corresponds to the + * question mark in the query that matches the position of the value in the + * array. You can also bind values using explicit keys, for instance + * array(":key"=>123) will bind the integer 123 to the key :key in the + * SQL. This method has no return value. + * + * @param string $sql SQL Code to execute + * @param array $aValues Values to bind to SQL query + * + * @return void + */ + public function Execute( $sql, $aValues=array() ) { + $this->runQuery($sql,$aValues); + return $this->affected_rows; + } + + /** + * Escapes a string for use in SQL using the currently selected + * PDO driver. + * + * @param string $string string to be escaped + * + * @return string $string escaped string + */ + public function Escape( $str ) { + $this->connect(); + return substr(substr($this->pdo->quote($str), 1), 0, -1); + } + + /** + * Returns the latest insert ID if driver does support this + * feature. + * + * @return integer $id primary key ID + */ + public function GetInsertID() { + $this->connect(); + return (int) $this->pdo->lastInsertId(); + } + + /** + * Returns the number of rows affected by the most recent query + * if the currently selected PDO driver supports this feature. + * + * @return integer $numOfRows number of rows affected + */ + public function Affected_Rows() { + $this->connect(); + return (int) $this->affected_rows; + } + + /** + * Toggles debug mode. In debug mode the driver will print all + * SQL to the screen together with some information about the + * results. All SQL code that passes through the driver will be + * passes on to the screen for inspection. + * This method has no return value. + * + * Additionally you can inject RedBean_Logger implementation + * where you can define your own log() method + * + * @param boolean $trueFalse turn on/off + * @param RedBean_Logger $logger + * + * @return void + */ + public function setDebugMode( $tf, $logger = NULL ) { + $this->connect(); + $this->debug = (bool)$tf; + if ($this->debug and !$logger) $logger = new RedBean_Logger_Default(); + $this->setLogger($logger); + } + + + /** + * Injects RedBean_Logger object. + * + * @param RedBean_Logger $logger + */ + public function setLogger( RedBean_Logger $logger ) { + $this->logger = $logger; + } + + /** + * Gets RedBean_Logger object. + * + * @return RedBean_Logger + */ + public function getLogger() { + return $this->logger; + } + + /** + * Starts a transaction. + * This method is part of the transaction mechanism of + * RedBeanPHP. All queries in a transaction are executed together. + * In case of an error all commands will be rolled back so none of the + * SQL in the transaction will affect the DB. Using transactions is + * considered best practice. + * This method has no return value. + * + * @return void + */ + public function StartTrans() { + $this->connect(); + $this->pdo->beginTransaction(); + } + + /** + * Commits a transaction. + * This method is part of the transaction mechanism of + * RedBeanPHP. All queries in a transaction are executed together. + * In case of an error all commands will be rolled back so none of the + * SQL in the transaction will affect the DB. Using transactions is + * considered best practice. + * This method has no return value. + * + * @return void + */ + public function CommitTrans() { + $this->connect(); + $this->pdo->commit(); + } + + /** + * Rolls back a transaction. + * This method is part of the transaction mechanism of + * RedBeanPHP. All queries in a transaction are executed together. + * In case of an error all commands will be rolled back so none of the + * SQL in the transaction will affect the DB. Using transactions is + * considered best practice. + * This method has no return value. + * + * @return void + */ + public function FailTrans() { + $this->connect(); + $this->pdo->rollback(); + } + + /** + * Returns the name of the database type/brand: i.e. mysql, db2 etc. + * + * @return string $typeName database identification + */ + public function getDatabaseType() { + $this->connect(); + return $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + } + + /** + * Returns the version number of the database. + * + * @return mixed $version version number of the database + */ + public function getDatabaseVersion() { + $this->connect(); + return $this->pdo->getAttribute(PDO::ATTR_CLIENT_VERSION); + } + + /** + * Returns the underlying PHP PDO instance. + * + * @return PDO $pdo PDO instance used by PDO wrapper + */ + public function getPDO() { + $this->connect(); + return $this->pdo; + } + + /** + * Closes database connection by destructing PDO. + */ + public function close() { + $this->pdo = null; + $this->isConnected = false; + } + + /** + * Returns TRUE if the current PDO instance is connected. + * + * @return boolean $yesNO + */ + public function isConnected() { + if (!$this->isConnected && !$this->pdo) return false; + return true; + } + + +} + + + +/** + * RedBean_OODBBean (Object Oriented DataBase Bean) + * + * @file RedBean/RedBean_OODBBean.php + * @desc The Bean class used for passing information + * @author Gabor de Mooij and the RedBeanPHP community + * @license BSD/GPLv2 + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_OODBBean implements IteratorAggregate, ArrayAccess, Countable { + + + /** + * By default own-lists and shared-lists no longer have IDs as keys (3.3+), + * this is because exportAll also does not offer this feature and we want the + * ORM to be more consistent. Also, exporting without keys makes it easier to + * export lists to Javascript because unlike in PHP in JS arrays will fill up gaps. + * + * @var boolean + */ + private static $flagKeyedExport = false; + + /** + * Reference to NULL property for magic getter. + * @var Null $null + */ + private $null = null; + + + /** + * Properties of the bean. These are kept in a private + * array called properties and exposed through the array interface. + * @var array $properties + */ + private $properties = array(); + + /** + * Meta Data storage. This is the internal property where all + * Meta information gets stored. + * @var array + */ + private $__info = NULL; + + /** + * Contains a BeanHelper to access service objects like + * te association manager and OODB. + * @var RedBean_BeanHelper + */ + private $beanHelper = NULL; + + /** + * Contains the latest Fetch Type. + * A Fetch Type is a preferred type for the next nested bean. + * @var null + */ + private $fetchType = NULL; + + /** + * Used store store SQL snippet for use with with() + * method. + * + * @var string + */ + private $withSql = ''; + + /** + * Alias name for a type. + * + * @var string + */ + private $aliasName = NULL; + + /** + * By default own-lists and shared-lists no longer have IDs as keys (3.3+), + * this is because exportAll also does not offer this feature and we want the + * ORM to be more consistent. Also, exporting without keys makes it easier to + * export lists to Javascript because unlike in PHP in JS arrays will fill up gaps. + * + * @var boolean $yesNo + */ + public static function setFlagKeyedExport($flag) { + self::$flagKeyedExport = (boolean) $flag; + } + + /** Returns the alias for a type + * + * @param $type aliased type + * + * @return string $type type + */ + private function getAlias( $type ) { + if ($this->fetchType) { + $type = $this->fetchType; + $this->fetchType = null; + } + return $type; + } + + /** + * Sets the Bean Helper. Normally the Bean Helper is set by OODB. + * Here you can change the Bean Helper. The Bean Helper is an object + * providing access to a toolbox for the bean necessary to retrieve + * nested beans (bean lists: ownBean,sharedBean) without the need to + * rely on static calls to the facade (or make this class dep. on OODB). + * + * @param RedBean_IBeanHelper $helper + * @return void + */ + public function setBeanHelper(RedBean_BeanHelper $helper) { + $this->beanHelper = $helper; + } + + + /** + * Returns an ArrayIterator so you can treat the bean like + * an array with the properties container as its contents. + * + * @return ArrayIterator $arrayIt an array iterator instance with $properties + */ + public function getIterator() { + return new ArrayIterator($this->properties); + } + + /** + * Imports all values in associative array $array. Every key is used + * for a property and every value will be assigned to the property + * identified by the key. So basically this method converts the + * associative array to a bean by loading the array. You can filter + * the values using the $selection parameter. If $selection is boolean + * false, no filtering will be applied. If $selection is an array + * only the properties specified (as values) in the $selection + * array will be taken into account. To skip a property, omit it from + * the $selection array. Also, instead of providing an array you may + * pass a comma separated list of property names. This method is + * chainable because it returns its own object. + * Imports data into bean + * + * @param array $array what you want to import + * @param string|array $selection selection of values + * @param boolean $notrim if TRUE values will not be trimmed + * + * @return RedBean_OODBBean $this + */ + public function import( $arr, $selection=false, $notrim=false ) { + if (is_string($selection)) $selection = explode(',',$selection); + //trim whitespaces + if (!$notrim && is_array($selection)) foreach($selection as $k=>$s){ $selection[$k]=trim($s); } + foreach($arr as $k=>$v) { + if ($k!='__info') { + if (!$selection || ($selection && in_array($k,$selection))) { + $this->$k = $v; + } + } + } + return $this; + } + + /** + * Injects the properties of another bean but keeps the original ID. + * Just like import() but keeps the original ID. + * Chainable. + * + * @param RedBean_OODBBean $otherBean the bean whose properties you would like to copy + * + * @return RedBean_OODBBean $self + */ + public function inject(RedBean_OODBBean $otherBean) { + $myID = $this->id; + $array = $otherBean->export(); + $this->import($array); + $this->id = $myID; + return $this; + } + + /** + * Very superficial export function + * @return array $properties + */ + public function getProperties() { + return $this->properties; + } + + /** + * Exports the bean as an array. + * This function exports the contents of a bean to an array and returns + * the resulting array. If $meta eq uals boolean TRUE, then the array will + * also contain the __info section containing the meta data inside the + * RedBean_OODBBean Bean object. + * @param boolean $meta + * @return array $arr + */ + public function export($meta = false, $parents = false, $onlyMe = false, $filters = array()) { + $arr=array(); + if ($parents) { + foreach($this as $k=>$v) { + if (substr($k,-3)=='_id') { + $prop = substr($k,0,strlen($k)-3); + $this->$prop; + } + } + } + foreach($this as $k=>$v) { + if (!$onlyMe && is_array($v)) { + $vn = array(); + foreach($v as $i=>$b) { + if (is_numeric($i) && !self::$flagKeyedExport) { + $vn[]=$b->export($meta,false,false,$filters); + } + else { + $vn[$i]=$b->export($meta,false,false,$filters); + } + $v = $vn; + } + } + elseif ($v instanceof RedBean_OODBBean) { + if (is_array($filters) && count($filters) && !in_array(strtolower($v->getMeta('type')),$filters)) { + continue; + } + $v = $v->export($meta,$parents,false,$filters); + } + $arr[$k] = $v; + } + if ($meta) $arr['__info'] = $this->__info; + return $arr; + } + + /** + * Exports the bean to an object. + * This function exports the contents of a bean to an object. + * @param object $obj + * @return array $arr + */ + public function exportToObj($obj) { + foreach($this->properties as $k=>$v) { + if (!is_array($v) && !is_object($v)) + $obj->$k = $v; + } + } + + /** + * Implements isset() function for use as an array. + * Returns whether bean has an element with key + * named $property. Returns TRUE if such an element exists + * and FALSE otherwise. + * @param string $property + * @return boolean $hasProperty + */ + public function __isset($property) { + return (isset($this->properties[$property])); + } + + + + /** + * Returns the ID of the bean no matter what the ID field is. + * + * @return string $id record Identifier for bean + */ + public function getID() { + return (string) $this->id; + } + + /** + * Unsets a property. This method will load the property first using + * __get. + * + * @param string $property property + * + * @return void + */ + public function __unset($property) { + $this->__get($property); + $fieldLink = $property.'_id'; + if (isset($this->$fieldLink)) { + //wanna unset a bean reference? + $this->$fieldLink = null; + } + if ((isset($this->properties[$property]))) { + unset($this->properties[$property]); + } + } + + /** + * Removes a property from the properties list without invoking + * an __unset on the bean. + * + * @param string $property property that needs to be unset + * + * @return void + */ + public function removeProperty( $property ) { + unset($this->properties[$property]); + } + + /** + * Adds WHERE clause conditions to ownList retrieval. + * For instance to get the pages that belong to a book you would + * issue the following command: $book->ownPage + * However, to order these pages by number use: + * + * $book->with(' ORDER BY `number` ASC ')->ownPage + * + * the additional SQL snippet will be merged into the final + * query. + * + * @param string $sql SQL to be added to retrieval query. + * + * @return RedBean_OODBBean $self + */ + public function with($sql) { + $this->withSql = $sql; + return $this; + } + + /** + * Just like with(). Except that this method prepends the SQL query snippet + * with AND which makes it slightly more comfortable to use a conditional + * SQL snippet. For instance to filter an own-list with pages (belonging to + * a book) on specific chapters you can use: + * + * $book->withCondition(' chapter = 3 ')->ownPage + * + * This will return in the own list only the pages having 'chapter == 3'. + * + * @param string $sql SQL to be added to retrieval query (prefixed by AND) + * + * @return RedBean_OODBBean $self + */ + public function withCondition($sql) { + $this->withSql = ' AND '.$sql; + return $this; + } + + /** + * Prepares an own-list to use an alias. This is best explained using + * an example. Imagine a project and a person. The project always involves + * two persons: a teacher and a student. The person beans have been aliased in this + * case, so to the project has a teacher_id pointing to a person, and a student_id + * also pointing to a person. Given a project, we obtain the teacher like this: + * + * $project->fetchAs('person')->teacher; + * + * Now, if we want all projects of a teacher we cant say: + * + * $teacher->ownProject + * + * because the $teacher is a bean of type 'person' and no project has been + * assigned to a person. Instead we use the alias() method like this: + * + * $teacher->alias('teacher')->ownProject + * + * now we get the projects associated with the person bean aliased as + * a teacher. + * + * @param string $aliasName the alias name to use + * + * @return RedBean_OODBBean + */ + public function alias($aliasName) { + $this->aliasName = $aliasName; + return $this; + } + + /** + * Magic Getter. Gets the value for a specific property in the bean. + * If the property does not exist this getter will make sure no error + * occurs. This is because RedBean allows you to query (probe) for + * properties. If the property can not be found this method will + * return NULL instead. + * @param string $property + * @return mixed $value + */ + public function &__get( $property ) { + if ($this->beanHelper) + $toolbox = $this->beanHelper->getToolbox(); + if ($this->withSql!=='') { + if (strpos($property,'own')===0) { + unset($this->properties[$property]); + } + } + if (!isset($this->properties[$property])) { + $fieldLink = $property.'_id'; + /** + * All this magic can be become very complex quicly. For instance, + * my PHP CLI produced a segfault while testing this code. Turns out that + * if fieldlink equals idfield, scripts tend to recusrively load beans and + * instead of giving a clue they simply crash and burn isnt that nice? + */ + if (isset($this->$fieldLink) && $fieldLink != $this->getMeta('sys.idfield')) { + $this->setMeta('tainted',true); + $type = $this->getAlias($property); + $targetType = $this->properties[$fieldLink]; + $bean = $toolbox->getRedBean()->load($type,$targetType); + //return $bean; + $this->properties[$property] = $bean; + return $this->properties[$property]; + } + if (strpos($property,'own')===0) { + $firstCharCode = ord(substr($property,3,1)); + if ($firstCharCode>=65 && $firstCharCode<=90) { + $type = (__lcfirst(str_replace('own','',$property))); + if ($this->aliasName) { + $myFieldLink = $this->aliasName.'_id'; + $this->setMeta('sys.alias.'.$type,$this->aliasName); + $this->aliasName = null; + } + else { + $myFieldLink = $this->getMeta('type').'_id'; + } + $beans = $toolbox->getRedBean()->find($type,array(),array(" $myFieldLink = ? ".$this->withSql,array($this->getID()))); + $this->withSql = ''; + $this->properties[$property] = $beans; + $this->setMeta('sys.shadow.'.$property,$beans); + $this->setMeta('tainted',true); + return $this->properties[$property]; + } + } + if (strpos($property,'shared')===0) { + $firstCharCode = ord(substr($property,6,1)); + if ($firstCharCode>=65 && $firstCharCode<=90) { + $type = (__lcfirst(str_replace('shared','',$property))); + $keys = $toolbox->getRedBean()->getAssociationManager()->related($this,$type); + if (!count($keys)) $beans = array(); else + $beans = $toolbox->getRedBean()->batch($type,$keys); + $this->properties[$property] = $beans; + $this->setMeta('sys.shadow.'.$property,$beans); + $this->setMeta('tainted',true); + return $this->properties[$property]; + } + } + return $this->null; + } + return $this->properties[$property]; + } + + /** + * Magic Setter. Sets the value for a specific property. + * This setter acts as a hook for OODB to mark beans as tainted. + * The tainted meta property can be retrieved using getMeta("tainted"). + * The tainted meta property indicates whether a bean has been modified and + * can be used in various caching mechanisms. + * @param string $property + * @param mixed $value + */ + + public function __set($property,$value) { + $this->__get($property); + $this->setMeta('tainted',true); + $linkField = $property.'_id'; + if (isset($this->properties[$linkField]) && !($value instanceof RedBean_OODBBean)) { + if (is_null($value) || $value === false) { + return $this->__unset($property); + } + else { + throw new RedBean_Exception_Security('Cannot cast to bean.'); + } + } + if ($value===false) { + $value = '0'; + } + elseif ($value===true) { + $value = '1'; + } + elseif ($value instanceof DateTime) { + $value = $value->format('Y-m-d H:i:s'); + } + $this->properties[$property] = $value; + } + + /** + * Sets a property directly, for internal use only. + * + * @param string $property property + * @param mixed $value value + */ + public function setProperty($property,$value) { + $this->properties[$property] = $value; + } + + + /** + * Returns the value of a meta property. A meta property + * contains extra information about the bean object that will not + * get stored in the database. Meta information is used to instruct + * RedBean as well as other systems how to deal with the bean. + * For instance: $bean->setMeta("buildcommand.unique", array( + * array("column1", "column2", "column3") ) ); + * Will add a UNIQUE constaint for the bean on columns: column1, column2 and + * column 3. + * To access a Meta property we use a dot separated notation. + * If the property cannot be found this getter will return NULL instead. + * @param string $path + * @param mixed $default + * @return mixed $value + */ + public function getMeta($path,$default = NULL) { + return (isset($this->__info[$path])) ? $this->__info[$path] : $default; + } + + /** + * Stores a value in the specified Meta information property. $value contains + * the value you want to store in the Meta section of the bean and $path + * specifies the dot separated path to the property. For instance "my.meta.property". + * If "my" and "meta" do not exist they will be created automatically. + * @param string $path + * @param mixed $value + */ + public function setMeta($path,$value) { + $this->__info[$path] = $value; + } + + /** + * Copies the meta information of the specified bean + * This is a convenience method to enable you to + * exchange meta information easily. + * @param RedBean_OODBBean $bean + * @return RedBean_OODBBean + */ + public function copyMetaFrom(RedBean_OODBBean $bean) { + $this->__info = $bean->__info; + return $this; + } + + + /** + * Reroutes a call to Model if exists. (new fuse) + * @param string $method + * @param array $args + * @return mixed $mixed + */ + public function __call($method, $args) { + if (!isset($this->__info['model'])) { + $model = $this->beanHelper->getModelForBean($this); + if (!$model) return; + $this->__info['model'] = $model; + } + if (!method_exists($this->__info['model'],$method)) return null; + return call_user_func_array(array($this->__info['model'],$method), $args); + } + + /** + * Implementation of __toString Method + * Routes call to Model. + * @return string $string + */ + public function __toString() { + $string = $this->__call('__toString',array()); + if ($string === null) { + return json_encode($this->properties); + } + else { + return $string; + } + } + + /** + * Implementation of Array Access Interface, you can access bean objects + * like an array. + * Call gets routed to __set. + * + * @param mixed $offset offset string + * @param mixed $value value + * + * @return void + */ + public function offsetSet($offset, $value) { + $this->__set($offset, $value); + } + + /** + * Implementation of Array Access Interface, you can access bean objects + * like an array. + * + * @param mixed $offset property + * + * @return + */ + public function offsetExists($offset) { + return isset($this->properties[$offset]); + } + + /** + * Implementation of Array Access Interface, you can access bean objects + * like an array. + * Unsets a value from the array/bean. + * + * @param mixed $offset property + * + * @return + */ + public function offsetUnset($offset) { + unset($this->properties[$offset]); + } + + /** + * Implementation of Array Access Interface, you can access bean objects + * like an array. + * Returns value of a property. + * + * @param mixed $offset property + * + * @return + */ + public function offsetGet($offset) { + return $this->__get($offset); + } + + /** + * Chainable method to cast a certain ID to a bean; for instance: + * $person = $club->fetchAs('person')->member; + * This will load a bean of type person using member_id as ID. + * + * @param string $type preferred fetch type + * + * @return RedBean_OODBBean + */ + public function fetchAs($type) { + $this->fetchType = $type; + return $this; + } + + /** + * Implementation of Countable interface. Makes it possible to use + * count() function on a bean. + * + * @return integer $numberOfProperties number of properties in the bean. + */ + public function count() { + return count($this->properties); + } + + /** + * Checks wether a bean is empty or not. + * A bean is empty if it has no other properties than the id field OR + * if all the other property are empty(). + * + * @return boolean + */ + public function isEmpty() { + $empty = true; + foreach($this->properties as $key=>$value) { + if ($key=='id') continue; + if (!empty($value)) { + $empty = false; + + } + } + return $empty; + } + + + /** + * Chainable setter. + * + * @param string $property the property of the bean + * @param mixed $value the value you want to set + * + * @return RedBean_OODBBean the bean + */ + public function setAttr($property,$value) { + $this->$property = $value; + return $this; + } + + /** + * Comfort method. + * Unsets all properties in array. + * + * @param array $properties properties you want to unset. + * + * @return RedBean_OODBBean + */ + public function unsetAll($properties) { + foreach($properties as $prop) { + if (isset($this->properties[$prop])) { + unset($this->properties[$prop]); + } + } + return $this; + } + + /** + * Returns original (old) value of a property. + * You can use this method to see what has changed in a + * bean. + * + * @param string $property name of the property you want the old value of + * + * @return mixed + */ + public function old($property) { + $old = $this->getMeta('sys.orig',array()); + if (isset($old[$property])) { + return $old[$property]; + } + } + + /** + * Convenience method. + * Returns true if the bean has been changed, or false otherwise. + * Same as $bean->getMeta('tainted'); + * Note that a bean becomes tainted as soon as you retrieve a list from + * the bean. This is because the bean lists are arrays and the bean cannot + * determine whether you have made modifications to a list so RedBeanPHP + * will mark the whole bean as tainted. + * + * @return boolean + */ + public function isTainted() { + return $this->getMeta('tainted'); + } + + + /** + * Returns TRUE if the value of a certain property of the bean has been changed and + * FALSE otherwise. + * + * @param string $property name of the property you want the change-status of + * + * @return boolean + */ + public function hasChanged($property) { + if (!isset($this->properties[$property])) return false; + return ($this->old($property)!=$this->properties[$property]); + } +} + + + + +/** + * Observable + * Base class for Observables + * + * @file RedBean/Observable.php + * @description Part of the observer pattern in RedBean + * @author Gabor de Mooij and the RedBeanPHP community + * @license BSD/GPLv2 + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +abstract class RedBean_Observable { + /** + * Array that keeps track of observers. + * @var array + */ + private $observers = array(); + + /** + * Implementation of the Observer Pattern. + * Adds a listener to this instance. + * This method can be used to attach an observer to an object. + * You can subscribe to a specific event by providing the ID + * of the event you are interested in. Once the event occurs + * the observable will notify the listeners by calling + * onEvent(); providing the event ID and either a bean or + * an information array. + * + * @param string $eventname event + * @param RedBean_Observer $observer observer + * + * @return void + */ + public function addEventListener( $eventname, RedBean_Observer $observer ) { + if (!isset($this->observers[ $eventname ])) { + $this->observers[ $eventname ] = array(); + } + foreach($this->observers[$eventname] as $o) if ($o==$observer) return; + $this->observers[ $eventname ][] = $observer; + } + + /** + * Implementation of the Observer Pattern. + * Sends an event (signal) to the registered listeners + * This method is provided by the abstract class Observable for + * convience. Observables can use this method to notify their + * observers by sending an event ID and information parameter. + * + * @param string $eventname eventname + * @param mixed $info info + * @return unknown_ty + */ + public function signal( $eventname, $info ) { + if (!isset($this->observers[ $eventname ])) { + $this->observers[ $eventname ] = array(); + } + foreach($this->observers[$eventname] as $observer) { + $observer->onEvent( $eventname, $info ); + } + } +} + +/** + * Observer + * + * @file RedBean/Observer.php + * @desc Part of the observer pattern in RedBean + * @author Gabor de Mooijand the RedBeanPHP community + * @license BSD/GPLv2 + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +interface RedBean_Observer { + + /** + * Part of the RedBean Observer Infrastructure. + * The on-event method is called by an observable once the + * event the observer has been registered for occurs. + * Once the even occurs, the observable will signal the observer + * using this method, sending the event name and the bean or + * an information array. + * + * @param string $eventname + * @param RedBean_OODBBean mixed $info + */ + public function onEvent( $eventname, $bean ); +} + +/** + * Adapter Interface + * + * @file RedBean/Adapter.php + * @desc Describes the API for a RedBean Database Adapter. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + * + */ +interface RedBean_Adapter { + + /** + * Returns the latest SQL statement + * + * @return string $SQLString SQLString + */ + public function getSQL(); + + /** + * Escapes a value for usage in an SQL statement + * + * @param string $sqlvalue value + */ + public function escape( $sqlvalue ); + + /** + * Executes an SQL Statement using an array of values to bind + * If $noevent is TRUE then this function will not signal its + * observers to notify about the SQL execution; this to prevent + * infinite recursion when using observers. + * + * @param string $sql SQL + * @param array $aValues values + * @param boolean $noevent no event firing + */ + public function exec( $sql , $aValues=array(), $noevent=false); + + /** + * Executes an SQL Query and returns a resultset. + * This method returns a multi dimensional resultset similar to getAll + * The values array can be used to bind values to the place holders in the + * SQL query. + * + * @param string $sql SQL + * @param array $aValues values + */ + public function get( $sql, $aValues = array() ); + + /** + * Executes an SQL Query and returns a resultset. + * This method returns a single row (one array) resultset. + * The values array can be used to bind values to the place holders in the + * SQL query. + * + * @param string $sql SQL + * @param array $aValues values to bind + * + * @return array $aMultiDimArray row + */ + public function getRow( $sql, $aValues = array() ); + + /** + * Executes an SQL Query and returns a resultset. + * This method returns a single column (one array) resultset. + * The values array can be used to bind values to the place holders in the + * SQL query. + * + * @param string $sql SQL + * @param array $aValues values to bind + * + * @return array $aSingleDimArray column + */ + public function getCol( $sql, $aValues = array() ); + + /** + * Executes an SQL Query and returns a resultset. + * This method returns a single cell, a scalar value as the resultset. + * The values array can be used to bind values to the place holders in the + * SQL query. + * + * @param string $sql SQL + * @param array $aValues values to bind + * + * @return string $sSingleValue value from cell + */ + public function getCell( $sql, $aValues = array() ); + + /** + * Executes the SQL query specified in $sql and takes + * the first two columns of the resultset. This function transforms the + * resultset into an associative array. Values from the the first column will + * serve as keys while the values of the second column will be used as values. + * The values array can be used to bind values to the place holders in the + * SQL query. + * + * @param string $sql SQL + * @param array $values values to bind + * + * @return array $associativeArray associative array result set + */ + public function getAssoc( $sql, $values = array() ); + + /** + * Returns the latest insert ID. + * + * @return integer $id primary key ID + */ + public function getInsertID(); + + /** + * Returns the number of rows that have been + * affected by the last update statement. + * + * @return integer $count number of rows affected + */ + public function getAffectedRows(); + + /** + * Returns the original database resource. This is useful if you want to + * perform operations on the driver directly instead of working with the + * adapter. RedBean will only access the adapter and never to talk + * directly to the driver though. + * + * @return object $driver driver + */ + public function getDatabase(); + + /** + * This method is part of the RedBean Transaction Management + * mechanisms. + * Starts a transaction. + */ + public function startTransaction(); + + /** + * This method is part of the RedBean Transaction Management + * mechanisms. + * Commits the transaction. + */ + public function commit(); + + /** + * This method is part of the RedBean Transaction Management + * mechanisms. + * Rolls back the transaction. + */ + public function rollback(); + + /** + * Closes database connection. + */ + public function close(); + +} + +/** + * DBAdapter (Database Adapter) + * @file RedBean/Adapter/DBAdapter.php + * @desc An adapter class to connect various database systems to RedBean + * @author Gabor de Mooij and the RedBeanPHP Community. + * @license BSD/GPLv2 + * + * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_Adapter_DBAdapter extends RedBean_Observable implements RedBean_Adapter { + + /** + * ADODB compatible class + * @var RedBean_Driver + */ + private $db = null; + + /** + * Contains SQL snippet + * @var string + */ + private $sql = ''; + + + /** + * Constructor. + * Creates an instance of the RedBean Adapter Class. + * This class provides an interface for RedBean to work + * with ADO compatible DB instances. + * + * @param RedBean_Driver $database ADO Compatible DB Instance + */ + public function __construct($database) { + $this->db = $database; + } + + /** + * Returns the latest SQL Statement. + * This method returns the most recently executed SQL statement string. + * This can be used for building logging features. + * + * @return string $SQL latest SQL statement + */ + public function getSQL() { + return $this->sql; + } + + /** + * Escapes a string for use in a Query. + * This method escapes the value argument using the native + * driver escaping functions. + * + * @param string $sqlvalue SQL value to escape + * + * @return string $escapedValue escaped value + */ + public function escape( $sqlvalue ) { + return $this->db->Escape($sqlvalue); + } + + /** + * Executes SQL code; any query without + * returning a resultset. + * This function allows you to provide an array with values to bind + * to query parameters. For instance you can bind values to question + * marks in the query. Each value in the array corresponds to the + * question mark in the query that matches the position of the value in the + * array. You can also bind values using explicit keys, for instance + * array(":key"=>123) will bind the integer 123 to the key :key in the + * SQL. + * + * @param string $sql SQL Code to execute + * @param array $values assoc. array binding values + * @param boolean $noevent if TRUE this will suppress the event 'sql_exec' + * + * @return mixed $undefSet whatever driver returns, undefined + */ + public function exec( $sql , $aValues=array(), $noevent=false) { + if (!$noevent) { + $this->sql = $sql; + $this->signal('sql_exec', $this); + } + return $this->db->Execute( $sql, $aValues ); + } + + /** + * Multi array SQL fetch. Fetches a multi dimensional array. + * This function allows you to provide an array with values to bind + * to query parameters. For instance you can bind values to question + * marks in the query. Each value in the array corresponds to the + * question mark in the query that matches the position of the value in the + * array. You can also bind values using explicit keys, for instance + * array(":key"=>123) will bind the integer 123 to the key :key in the + * SQL. + * + * @param string $sql SQL code to execute + * @param array $values assoc. array binding values + * + * @return array $result two dimensional array result set + */ + public function get( $sql, $aValues = array() ) { + $this->sql = $sql; + $this->signal('sql_exec', $this); + return $this->db->GetAll( $sql,$aValues ); + } + + /** + * Executes SQL and fetches a single row. + * This function allows you to provide an array with values to bind + * to query parameters. For instance you can bind values to question + * marks in the query. Each value in the array corresponds to the + * question mark in the query that matches the position of the value in the + * array. You can also bind values using explicit keys, for instance + * array(":key"=>123) will bind the integer 123 to the key :key in the + * SQL. + * + * @param string $sql SQL code to execute + * @param array $values assoc. array binding values + * + * @return array $result one dimensional array result set + */ + public function getRow( $sql, $aValues = array() ) { + $this->sql = $sql; + $this->signal('sql_exec', $this); + return $this->db->GetRow( $sql,$aValues ); + } + + /** + * Executes SQL and returns a one dimensional array result set. + * This function rotates the result matrix to obtain a column result set. + * This function allows you to provide an array with values to bind + * to query parameters. For instance you can bind values to question + * marks in the query. Each value in the array corresponds to the + * question mark in the query that matches the position of the value in the + * array. You can also bind values using explicit keys, for instance + * array(":key"=>123) will bind the integer 123 to the key :key in the + * SQL. + * + * @param string $sql SQL code to execute + * @param array $values assoc. array binding values + * + * @return array $result one dimensional array result set + */ + public function getCol( $sql, $aValues = array() ) { + $this->sql = $sql; + $this->signal('sql_exec', $this); + return $this->db->GetCol( $sql,$aValues ); + } + + + /** + * Executes an SQL Query and fetches the first two columns only. + * Then this function builds an associative array using the first + * column for the keys and the second result column for the + * values. For instance: SELECT id, name FROM... will produce + * an array like: id => name. + * This function allows you to provide an array with values to bind + * to query parameters. For instance you can bind values to question + * marks in the query. Each value in the array corresponds to the + * question mark in the query that matches the position of the value in the + * array. You can also bind values using explicit keys, for instance + * array(":key"=>123) will bind the integer 123 to the key :key in the + * SQL. + * + * @param string $sql SQL code to execute + * @param array $values assoc. array binding values + * + * @return array $result multi dimensional assoc. array result set + */ + public function getAssoc( $sql, $aValues = array() ) { + $this->sql = $sql; + $this->signal('sql_exec', $this); + $rows = $this->db->GetAll( $sql, $aValues ); + $assoc = array(); + if ($rows) { + foreach($rows as $row) { + if (is_array($row) && count($row)>0) { + if (count($row)>1) { + $key = array_shift($row); + $value = array_shift($row); + } + elseif (count($row)==1) { + $key = array_shift($row); + $value=$key; + } + $assoc[ $key ] = $value; + } + } + } + return $assoc; + } + + + /** + * Retrieves a single cell. + * This function allows you to provide an array with values to bind + * to query parameters. For instance you can bind values to question + * marks in the query. Each value in the array corresponds to the + * question mark in the query that matches the position of the value in the + * array. You can also bind values using explicit keys, for instance + * array(":key"=>123) will bind the integer 123 to the key :key in the + * SQL. + * + * @param string $sql sql code to execute + * @param array $values assoc. array binding values + * + * @return array $result scalar result set + */ + + public function getCell( $sql, $aValues = array(), $noSignal = null ) { + $this->sql = $sql; + if (!$noSignal) $this->signal('sql_exec', $this); + $arr = $this->db->getCol( $sql, $aValues ); + if ($arr && is_array($arr)) return ($arr[0]); else return false; + } + + /** + * Returns latest insert id, most recently inserted id. + * Following an insert-SQL statement this method will return the most recently + * primary key ID of an inserted record. + * + * @return integer $id latest insert ID + */ + public function getInsertID() { + return $this->db->getInsertID(); + } + + /** + * Returns number of affected rows. + * Returns the number of rows that have been affected by the most recent + * SQL query. + * + * @return integer $numOfAffectRows + */ + public function getAffectedRows() { + return $this->db->Affected_Rows(); + } + + /** + * Unwrap the original database object. + * Returns the database driver instance. For instance this can be + * an OCI object or a PDO instance or some other third party driver. + * + * @return RedBean_Driver $database returns the inner database object + */ + public function getDatabase() { + return $this->db; + } + + /** + * Transactions. + * Part of the transaction management infrastructure of RedBeanPHP. + * Starts a transaction. + * Note that transactions may not work in fluid mode depending on your + * database platform. + */ + public function startTransaction() { + return $this->db->StartTrans(); + } + + /** + * Transactions. + * Part of the transaction management infrastructure of RedBeanPHP. + * Commits a transaction. + * Note that transactions may not work in fluid mode depending on your + * database platform. + */ + public function commit() { + return $this->db->CommitTrans(); + } + + /** + * Transactions. + * Part of the transaction management infrastructure of RedBeanPHP. + * Rolls back transaction. This will undo all changes that have been + * part of the transaction. + * Note that transactions may not work in fluid mode depending on your + * database platform. + */ + public function rollback() { + return $this->db->FailTrans(); + } + + /** + * Closes the database connection. + */ + public function close() { + $this->db->close(); + } +} + + +/** + * QueryWriter + * Interface for QueryWriters + * + * @file RedBean/QueryWriter.php + * @desc Describes the API for a QueryWriter + * @author Gabor de Mooij and the RedBeanPHP community + * @license BSD/GPLv2 + * + * Notes: + * - Whenever you see a parameter called $table or $type you should always + * be aware of the fact that this argument contains a Bean Type string, not the + * actual table name. These raw type names are passed to safeTable() to obtain the + * actual name of the database table. Don't let the names confuse you $type/$table + * refers to Bean Type, not physical database table names! + * - This is the interface for FLUID database drivers. Drivers intended to support + * just FROZEN mode should implement the IceWriter instead. + * + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +interface RedBean_QueryWriter { + + /** + * QueryWriter Constant Identifier. + * Identifies a situation in which a table has not been found in + * the database. + */ + const C_SQLSTATE_NO_SUCH_TABLE = 1; + + /** + * QueryWriter Constant Identifier. + * Identifies a situation in which a perticular column has not + * been found in the database. + */ + const C_SQLSTATE_NO_SUCH_COLUMN = 2; + + /** + * QueryWriter Constant Identifier. + * Identifies a situation in which a perticular column has not + * been found in the database. + */ + const C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION = 3; + + /** + * Returns the tables that are in the database. + * + * @return array $arrayOfTables list of tables + */ + public function getTables(); + + /** + * This method should create a table for the bean. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type type of bean you want to create a table for + * + * @return void + */ + public function createTable($type); + + /** + * Returns an array containing all the columns of the specified type. + * The format of the return array looks like this: + * $field => $type where $field is the name of the column and $type + * is a database specific description of the datatype. + * + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type type of bean you want to obtain a column list of + * + * @return array $listOfColumns list of columns ($field=>$type) + */ + public function getColumns($type); + + + /** + * Returns the Column Type Code (integer) that corresponds + * to the given value type. This method is used to determine the minimum + * column type required to represent the given value. + * + * @param string $value value + * + * @return integer $type type + */ + public function scanType($value, $alsoScanSpecialForTypes=false); + + /** + * This method should add a column to a table. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type name of the table + * @param string $column name of the column + * @param integer $field data type for field + * + * @return void + * + */ + public function addColumn($type, $column, $field); + + /** + * This method should return a data type constant based on the + * SQL type definition. This function is meant to compare column data + * type to check whether a column is wide enough to represent the desired + * values. + * + * @param integer $typedescription SQL type description from database + * + * @return integer $type + */ + public function code($typedescription); + + /** + * This method should widen the column to the specified data type. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type type / table that needs to be adjusted + * @param string $column column that needs to be altered + * @param integer $datatype target data type + * + * @return void + */ + public function widenColumn($type, $column, $datatype); + + /** + * This method should update (or insert a record), it takes + * a table name, a list of update values ( $field => $value ) and an + * primary key ID (optional). If no primary key ID is provided, an + * INSERT will take place. + * Returns the new ID. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type name of the table to update + * @param array $updatevalues list of update values + * @param integer $id optional primary key ID value + * + * @return integer $id the primary key ID value of the new record + */ + public function updateRecord($type, $updatevalues, $id=null); + + + /** + * This method should select a record. You should be able to provide a + * collection of conditions using the following format: + * array( $field1 => array($possibleValue1, $possibleValue2,... $possibleValueN ), + * ...$fieldN=>array(...)); + * Also, additional SQL can be provided. This SQL snippet will be appended to the + * query string. If the $delete parameter is set to TRUE instead of selecting the + * records they will be deleted. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type type of bean to select records from + * @param array $cond conditions using the specified format + * @param string $asql additional sql + * @param boolean $delete IF TRUE delete records (optional) + * @param boolean $inverse IF TRUE inverse the selection (optional) + * + * @return array $records selected records + */ + public function selectRecord($type, $conditions, $addSql = null, $delete = false, $inverse = false); + + + /** + * This method should add a UNIQUE constraint index to a table on columns $columns. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type type + * @param array $columnsPartOfIndex columns to include in index + * + * @return void + */ + public function addUniqueIndex($type,$columns); + + + /** + * This method should check whether the SQL state is in the list of specified states + * and returns true if it does appear in this list or false if it + * does not. The purpose of this method is to translate the database specific state to + * a one of the constants defined in this class and then check whether it is in the list + * of standard states provided. + * + * @param string $state sql state + * @param array $list list + * + * @return boolean $isInList + */ + public function sqlStateIn( $state, $list ); + + /** + * This method should remove all beans of a certain type. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type bean type + * + * @return void + */ + public function wipe($type); + + /** + * This method should count the number of beans of the given type. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type type of bean to count + * + * @return integer $numOfBeans number of beans found + */ + public function count($type); + + /** + * This method should filter a column name so that it can + * be used safely in a query for a specific database. + * + * @param string $name the column name + * @param bool $noQuotes whether you want to omit quotes + * + * @return string $clean the clean version of the column name + */ + public function safeColumn($name, $noQuotes = false); + + /** + * This method should filter a type name so that it can + * be used safely in a query for a specific database. It actually + * converts a type to a table. TYPE -> TABLE + * + * @param string $name the name of the type + * @param bool $noQuotes whether you want to omit quotes in table name + * + * @return string $tablename clean table name for use in query + */ + public function safeTable($name, $noQuotes = false); + + /** + * This method should add a constraint. If one of the beans gets trashed + * the other, related bean should be removed as well. + * + * @param RedBean_OODBBean $bean1 first bean + * @param RedBean_OODBBean $bean2 second bean + * + * @return void + */ + public function addConstraint( RedBean_OODBBean $bean1, RedBean_OODBBean $bean2 ); + + /** + * This method should add a foreign key from type and field to + * target type and target field. + * The foreign key is created without an action. On delete/update + * no action will be triggered. The FK is only used to allow database + * tools to generate pretty diagrams and to make it easy to add actions + * later on. + * This methods accepts a type and infers the corresponding table name. + * + * + * @param string $type type that will have a foreign key field + * @param string $targetType points to this type + * @param string $field field that contains the foreign key value + * @param string $targetField field where the fk points to + * + * @return void + */ + public function addFK( $type, $targetType, $field, $targetField); + + + /** + * This method should add an index to a type and field with name + * $name. + * This methods accepts a type and infers the corresponding table name. + * + * @param $type type to add index to + * @param $name name of the new index + * @param $column field to index + * + * @return void + */ + public function addIndex($type, $name, $column); + + /** + * Returns a modified value from ScanType. + * Used for special types. + * + * @return mixed $value changed value + */ + public function getValue(); + +} + +/** + * RedBean Abstract Query Writer + * + * @file RedBean/QueryWriter/AQueryWriter.php + * @description Quert Writer + * Represents an abstract Database to RedBean + * To write a driver for a different database for RedBean + * Contains a number of functions all implementors can + * inherit or override. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * + * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +abstract class RedBean_QueryWriter_AQueryWriter { + + /** + * Scanned value (scanType) + * @var type + */ + protected $svalue; + + /** + * Supported Column Types. + * @var array + */ + public $typeno_sqltype = array(); + + /** + * Holds a reference to the database adapter to be used. + * @var RedBean_Adapter_DBAdapter + */ + protected $adapter; + + + /** + * default value to for blank field (passed to PK for auto-increment) + * @var string + */ + protected $defaultValue = 'NULL'; + + /** + * character to escape keyword table/column names + * @var string + */ + protected $quoteCharacter = ''; + + + /** + * Constructor + * Sets the default Bean Formatter, use parent::__construct() in + * subclass to achieve this. + */ + public function __construct() { + + } + + /** + * Do everything that needs to be done to format a table name. + * + * @param string $name of table + * + * @return string table name + */ + public function safeTable($name, $noQuotes = false) { + $name = $this->check($name); + if (!$noQuotes) $name = $this->noKW($name); + return $name; + } + + /** + * Do everything that needs to be done to format a column name. + * + * @param string $name of column + * + * @return string $column name + */ + public function safeColumn($name, $noQuotes = false) { + $name = $this->check($name); + if (!$noQuotes) $name = $this->noKW($name); + return $name; + } + + /** + * Returns the sql that should follow an insert statement. + * + * @param string $table name + * + * @return string sql + */ + protected function getInsertSuffix ($table) { + return ''; + } + + /** + * Checks table name or column name. + * + * @param string $table table string + * + * @return string $table escaped string + */ + protected function check($table) { + if ($this->quoteCharacter && strpos($table, $this->quoteCharacter)!==false) { + throw new Redbean_Exception_Security('Illegal chars in table name'); + } + return $this->adapter->escape($table); + } + + /** + * Puts keyword escaping symbols around string. + * + * @param string $str keyword + * + * @return string $keywordSafeString escaped keyword + */ + protected function noKW($str) { + $q = $this->quoteCharacter; + return $q.$str.$q; + } + + /** + * This method adds a column to a table. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type name of the table + * @param string $column name of the column + * @param integer $field data type for field + * + * @return void + * + */ + public function addColumn( $type, $column, $field ) { + $table = $type; + $type = $field; + $table = $this->safeTable($table); + $column = $this->safeColumn($column); + $type = array_key_exists($type, $this->typeno_sqltype) ? $this->typeno_sqltype[$type] : ''; + $sql = "ALTER TABLE $table ADD $column $type "; + $this->adapter->exec( $sql ); + } + + /** + * This method updates (or inserts) a record, it takes + * a table name, a list of update values ( $field => $value ) and an + * primary key ID (optional). If no primary key ID is provided, an + * INSERT will take place. + * Returns the new ID. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type name of the table to update + * @param array $updatevalues list of update values + * @param integer $id optional primary key ID value + * + * @return integer $id the primary key ID value of the new record + */ + public function updateRecord( $type, $updatevalues, $id=null) { + $table = $type; + if (!$id) { + $insertcolumns = $insertvalues = array(); + foreach($updatevalues as $pair) { + $insertcolumns[] = $pair['property']; + $insertvalues[] = $pair['value']; + } + return $this->insertRecord($table,$insertcolumns,array($insertvalues)); + } + if ($id && !count($updatevalues)) return $id; + + $table = $this->safeTable($table); + $sql = "UPDATE $table SET "; + $p = $v = array(); + foreach($updatevalues as $uv) { + $p[] = " {$this->safeColumn($uv["property"])} = ? "; + $v[]=$uv['value']; + } + $sql .= implode(',', $p ) .' WHERE id = '.intval($id); + $this->adapter->exec( $sql, $v ); + return $id; + } + + /** + * Inserts a record into the database using a series of insert columns + * and corresponding insertvalues. Returns the insert id. + * + * @param string $table table to perform query on + * @param array $insertcolumns columns to be inserted + * @param array $insertvalues values to be inserted + * + * @return integer $insertid insert id from driver, new record id + */ + protected function insertRecord( $table, $insertcolumns, $insertvalues ) { + $default = $this->defaultValue; + $suffix = $this->getInsertSuffix($table); + $table = $this->safeTable($table); + if (count($insertvalues)>0 && is_array($insertvalues[0]) && count($insertvalues[0])>0) { + foreach($insertcolumns as $k=>$v) { + $insertcolumns[$k] = $this->safeColumn($v); + } + $insertSQL = "INSERT INTO $table ( id, ".implode(',',$insertcolumns)." ) VALUES + ( $default, ". implode(',',array_fill(0,count($insertcolumns),' ? '))." ) $suffix"; + + foreach($insertvalues as $i=>$insertvalue) { + $ids[] = $this->adapter->getCell( $insertSQL, $insertvalue, $i ); + } + $result = count($ids)===1 ? array_pop($ids) : $ids; + } + else { + $result = $this->adapter->getCell( "INSERT INTO $table (id) VALUES($default) $suffix"); + } + if ($suffix) return $result; + $last_id = $this->adapter->getInsertID(); + return $last_id; + } + + + + + /** + * This selects a record. You provide a + * collection of conditions using the following format: + * array( $field1 => array($possibleValue1, $possibleValue2,... $possibleValueN ), + * ...$fieldN=>array(...)); + * Also, additional SQL can be provided. This SQL snippet will be appended to the + * query string. If the $delete parameter is set to TRUE instead of selecting the + * records they will be deleted. + * This methods accepts a type and infers the corresponding table name. + * + * @throws Exception + * @param string $type type of bean to select records from + * @param array $cond conditions using the specified format + * @param string $asql additional sql + * @param boolean $delete IF TRUE delete records (optional) + * @param boolean $inverse IF TRUE inverse the selection (optional) + * @param boolean $all IF TRUE suppress WHERE keyword, omitting WHERE clause + * + * @return array $records selected records + */ + public function selectRecord( $type, $conditions, $addSql=null, $delete=null, $inverse=false, $all=false ) { + if (!is_array($conditions)) throw new Exception('Conditions must be an array'); + $table = $this->safeTable($type); + $sqlConditions = array(); + $bindings=array(); + foreach($conditions as $column=>$values) { + if (!count($values)) continue; + $sql = $this->safeColumn($column); + $sql .= ' '.($inverse ? ' NOT ':'').' IN ( '; + //If its safe to not use bindings please do... (fixes SQLite PDO issue limit 256 bindings) + if (is_array($conditions) + && count($conditions)===1 + && isset($conditions['id']) + && is_array($values) + && preg_match('/^\d+$/',implode('',$values))) { + $sql .= implode(',',$values).') '; + $sqlConditions[] = $sql; + } + else { + $sql .= implode(',',array_fill(0,count($values),'?')).') '; + $sqlConditions[] = $sql; + if (!is_array($values)) $values = array($values); + foreach($values as $k=>$v) { + $values[$k]=strval($v); + } + $bindings = array_merge($bindings,$values); + } + } + //$addSql can be either just a string or array($sql, $bindings) + if (is_array($addSql)) { + if (count($addSql)>1) { + $bindings = array_merge($bindings,$addSql[1]); + } + else { + $bindings = array(); + } + $addSql = $addSql[0]; + + } + $sql = ''; + if (is_array($sqlConditions) && count($sqlConditions)>0) { + $sql = implode(' AND ',$sqlConditions); + $sql = " WHERE ( $sql ) "; + if ($addSql) $sql .= " AND $addSql "; + } + elseif ($addSql) { + if ($all) { + $sql = " $addSql "; + } + else { + $sql = " WHERE $addSql "; + } + } + $sql = (($delete) ? 'DELETE FROM ' : 'SELECT * FROM ').$table.$sql; + $rows = $this->adapter->get($sql,$bindings); + return $rows; + } + + + /** + * This method removes all beans of a certain type. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type bean type + * + * @return void + */ + public function wipe($type) { + $table = $type; + $table = $this->safeTable($table); + $sql = "TRUNCATE $table "; + $this->adapter->exec($sql); + } + + /** + * Counts rows in a table. + * + * @param string $beanType type of bean to count + * @param string $addSQL additional SQL + * @param array $params parameters to bind to SQL + * + * @return integer $numRowsFound + */ + public function count($beanType,$addSQL = '',$params = array()) { + $sql = "SELECT count(*) FROM {$this->safeTable($beanType)} "; + if ($addSQL!='') $addSQL = ' WHERE '.$addSQL; + return (int) $this->adapter->getCell($sql.$addSQL,$params); + } + + + + /** + * This is a utility service method publicly available. + * It allows you to check whether you can safely treat an certain value as an integer by + * comparing an int-valled string representation with a default string casted string representation and + * a ctype-digit check. It does not take into account numerical limitations (X-bit INT), just that it + * can be treated like an INT. This is useful for binding parameters to query statements like + * Query Writers and drivers can do. + * + * @static + * + * @param string $value string representation of a certain value + * + * @return boolean $value boolean result of analysis + */ + public static function canBeTreatedAsInt( $value ) { + return (boolean) (ctype_digit(strval($value)) && strval($value)===strval(intval($value))); + } + + + /** + * This method adds a foreign key from type and field to + * target type and target field. + * The foreign key is created without an action. On delete/update + * no action will be triggered. The FK is only used to allow database + * tools to generate pretty diagrams and to make it easy to add actions + * later on. + * This methods accepts a type and infers the corresponding table name. + * + * + * @param string $type type that will have a foreign key field + * @param string $targetType points to this type + * @param string $field field that contains the foreign key value + * @param string $targetField field where the fk points to + * + * @return void + */ + public function addFK( $type, $targetType, $field, $targetField, $isDependent = false) { + $table = $this->safeTable($type); + $tableNoQ = $this->safeTable($type,true); + $targetTable = $this->safeTable($targetType); + $column = $this->safeColumn($field); + $columnNoQ = $this->safeColumn($field,true); + $targetColumn = $this->safeColumn($targetField); + $targetColumnNoQ = $this->safeColumn($targetField,true); + $db = $this->adapter->getCell('select database()'); + $fkName = 'fk_'.$tableNoQ.'_'.$columnNoQ.'_'.$targetColumnNoQ.($isDependent ? '_casc':''); + $cName = 'cons_'.$fkName; + $cfks = $this->adapter->getCell(" + SELECT CONSTRAINT_NAME + FROM information_schema.KEY_COLUMN_USAGE + WHERE TABLE_SCHEMA ='$db' AND TABLE_NAME = '$tableNoQ' AND COLUMN_NAME = '$columnNoQ' AND + CONSTRAINT_NAME <>'PRIMARY' AND REFERENCED_TABLE_NAME is not null + "); + $flagAddKey = false; + + try{ + //No keys + if (!$cfks) { + $flagAddKey = true; //go get a new key + } + //has fk, but different setting, --remove + if ($cfks && $cfks!=$cName) { + $this->adapter->exec("ALTER TABLE $table DROP FOREIGN KEY $cfks "); + $flagAddKey = true; //go get a new key. + } + if ($flagAddKey) { + $this->adapter->exec("ALTER TABLE $table + ADD CONSTRAINT $cName FOREIGN KEY $fkName ( $column ) REFERENCES $targetTable ( + $targetColumn) ON DELETE ".($isDependent ? 'CASCADE':'SET NULL').' ON UPDATE SET NULL ;'); + } + } + catch(Exception $e) { } //Failure of fk-constraints is not a problem + + } + + /** + * Returns the format for link tables. + * Given an array containing two type names this method returns the + * name of the link table to be used to store and retrieve + * association records. + * + * @param array $types two types array($type1,$type2) + * + * @return string $linktable name of the link table + */ + public static function getAssocTableFormat($types) { + sort($types); + return ( implode('_', $types) ); + } + + + /** + * Adds a constraint. If one of the beans gets trashed + * the other, related bean should be removed as well. + * + * @param RedBean_OODBBean $bean1 first bean + * @param RedBean_OODBBean $bean2 second bean + * @param bool $dontCache by default we use a cache, TRUE = NO CACHING (optional) + * + * @return void + */ + public function addConstraint( RedBean_OODBBean $bean1, RedBean_OODBBean $bean2) { + $table1 = $bean1->getMeta('type'); + $table2 = $bean2->getMeta('type'); + $writer = $this; + $adapter = $this->adapter; + $table = RedBean_QueryWriter_AQueryWriter::getAssocTableFormat( array( $table1,$table2) ); + + $property1 = $bean1->getMeta('type') . '_id'; + $property2 = $bean2->getMeta('type') . '_id'; + if ($property1==$property2) $property2 = $bean2->getMeta('type').'2_id'; + + $table = $adapter->escape($table); + $table1 = $adapter->escape($table1); + $table2 = $adapter->escape($table2); + $property1 = $adapter->escape($property1); + $property2 = $adapter->escape($property2); + + //Dispatch to right method + return $this->constrain($table, $table1, $table2, $property1, $property2); + } + + /** + * Checks whether a value starts with zeros. In this case + * the value should probably be stored using a text datatype instead of a + * numerical type in order to preserve the zeros. + * + * @param string $value value to be checked. + */ + protected function startsWithZeros($value) { + $value = strval($value); + if (strlen($value)>1 && strpos($value,'0')===0 && strpos($value,'0.')!==0) { + return true; + } + else { + return false; + } + } + + /** + * Returns a modified value from ScanType. + * Used for special types. + * + * @return mixed $value changed value + */ + public function getValue(){ + return $this->svalue; + } + +} + + +/** + * RedBean MySQLWriter + * + * @file RedBean/QueryWriter/MySQL.php + * @description Represents a MySQL Database to RedBean + * To write a driver for a different database for RedBean + * you should only have to change this file. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * + * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_QueryWriter_MySQL extends RedBean_QueryWriter_AQueryWriter implements RedBean_QueryWriter { + + /** + * Here we describe the datatypes that RedBean + * Uses internally. If you write a QueryWriter for + * RedBean you should provide a list of types like this. + */ + + /** + * DATA TYPE + * Boolean Data type + * @var integer + */ + const C_DATATYPE_BOOL = 0; + + /** + * + * DATA TYPE + * Unsigned 8BIT Integer + * @var integer + */ + const C_DATATYPE_UINT8 = 1; + + /** + * + * DATA TYPE + * Unsigned 32BIT Integer + * @var integer + */ + const C_DATATYPE_UINT32 = 2; + + /** + * DATA TYPE + * Double precision floating point number and + * negative numbers. + * @var integer + */ + const C_DATATYPE_DOUBLE = 3; + + /** + * DATA TYPE + * Standard Text column (like varchar255) + * At least 8BIT character support. + * @var integer + */ + const C_DATATYPE_TEXT8 = 4; + + /** + * DATA TYPE + * Long text column (16BIT) + * @var integer + */ + const C_DATATYPE_TEXT16 = 5; + + /** + * + * DATA TYPE + * 32BIT long textfield (number of characters can be as high as 32BIT) Data type + * This is the biggest column that RedBean supports. If possible you may write + * an implementation that stores even bigger values. + * @var integer + */ + const C_DATATYPE_TEXT32 = 6; + + /** + * Special type date for storing date values: YYYY-MM-DD + * @var integer + */ + const C_DATATYPE_SPECIAL_DATE = 80; + + /** + * Special type datetime for store date-time values: YYYY-MM-DD HH:II:SS + * @var integer + */ + const C_DATATYPE_SPECIAL_DATETIME = 81; + + + /** + * + * DATA TYPE + * Specified. This means the developer or DBA + * has altered the column to a different type not + * recognized by RedBean. This high number makes sure + * it will not be converted back to another type by accident. + * @var integer + */ + const C_DATATYPE_SPECIFIED = 99; + + + + /** + * Holds the RedBean Database Adapter. + * @var RedBean_Adapter_DBAdapter + */ + protected $adapter; + + /** + * character to escape keyword table/column names + * @var string + */ + protected $quoteCharacter = '`'; + + /** + * Constructor. + * The Query Writer Constructor also sets up the database. + * + * @param RedBean_Adapter_DBAdapter $adapter adapter + * + */ + public function __construct( RedBean_Adapter $adapter ) { + + $this->typeno_sqltype = array( + RedBean_QueryWriter_MySQL::C_DATATYPE_BOOL=>" SET('1') ", + RedBean_QueryWriter_MySQL::C_DATATYPE_UINT8=>' TINYINT(3) UNSIGNED ', + RedBean_QueryWriter_MySQL::C_DATATYPE_UINT32=>' INT(11) UNSIGNED ', + RedBean_QueryWriter_MySQL::C_DATATYPE_DOUBLE=>' DOUBLE ', + RedBean_QueryWriter_MySQL::C_DATATYPE_TEXT8=>' VARCHAR(255) ', + RedBean_QueryWriter_MySQL::C_DATATYPE_TEXT16=>' TEXT ', + RedBean_QueryWriter_MySQL::C_DATATYPE_TEXT32=>' LONGTEXT ', + RedBean_QueryWriter_MySQL::C_DATATYPE_SPECIAL_DATE=>' DATE ', + RedBean_QueryWriter_MySQL::C_DATATYPE_SPECIAL_DATETIME=>' DATETIME ', + ); + + $this->sqltype_typeno = array(); + foreach($this->typeno_sqltype as $k=>$v) + $this->sqltype_typeno[trim(strtolower($v))]=$k; + + + $this->adapter = $adapter; + parent::__construct(); + } + + /** + * This method returns the datatype to be used for primary key IDS and + * foreign keys. Returns one if the data type constants. + * + * @return integer $const data type to be used for IDS. + */ + public function getTypeForID() { + return self::C_DATATYPE_UINT32; + } + + /** + * Returns all tables in the database. + * + * @return array $tables tables + */ + public function getTables() { + return $this->adapter->getCol( 'show tables' ); + } + + /** + * Creates an empty, column-less table for a bean based on it's type. + * This function creates an empty table for a bean. It uses the + * safeTable() function to convert the type name to a table name. + * + * @param string $table type of bean you want to create a table for + * + * @return void + */ + public function createTable( $table ) { + $table = $this->safeTable($table); + $sql = " CREATE TABLE $table ( + id INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT , + PRIMARY KEY ( id ) + ) ENGINE = InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci "; + $this->adapter->exec( $sql ); + } + + /** + * Returns an array containing the column names of the specified table. + * + * @param string $table table + * + * @return array $columns columns + */ + public function getColumns( $table ) { + $table = $this->safeTable($table); + $columnsRaw = $this->adapter->get("DESCRIBE $table"); + foreach($columnsRaw as $r) { + $columns[$r['Field']]=$r['Type']; + } + return $columns; + } + + /** + * Returns the MySQL Column Type Code (integer) that corresponds + * to the given value type. + * + * @param string $value value + * + * @return integer $type type + */ + public function scanType( $value, $flagSpecial=false ) { + $this->svalue = $value; + + if (is_null($value)) { + return RedBean_QueryWriter_MySQL::C_DATATYPE_BOOL; + } + + if ($flagSpecial) { + if (preg_match('/^\d{4}\-\d\d-\d\d$/',$value)) { + return RedBean_QueryWriter_MySQL::C_DATATYPE_SPECIAL_DATE; + } + if (preg_match('/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d$/',$value)) { + return RedBean_QueryWriter_MySQL::C_DATATYPE_SPECIAL_DATETIME; + } + } + $value = strval($value); + if (!$this->startsWithZeros($value)) { + + if ($value=='1' || $value=='') { + return RedBean_QueryWriter_MySQL::C_DATATYPE_BOOL; + } + if (is_numeric($value) && (floor($value)==$value) && $value >= 0 && $value <= 255 ) { + return RedBean_QueryWriter_MySQL::C_DATATYPE_UINT8; + } + if (is_numeric($value) && (floor($value)==$value) && $value >= 0 && $value <= 4294967295 ) { + return RedBean_QueryWriter_MySQL::C_DATATYPE_UINT32; + } + if (is_numeric($value)) { + return RedBean_QueryWriter_MySQL::C_DATATYPE_DOUBLE; + } + } + if (strlen($value) <= 255) { + return RedBean_QueryWriter_MySQL::C_DATATYPE_TEXT8; + } + if (strlen($value) <= 65535) { + return RedBean_QueryWriter_MySQL::C_DATATYPE_TEXT16; + } + return RedBean_QueryWriter_MySQL::C_DATATYPE_TEXT32; + } + + /** + * Returns the Type Code for a Column Description. + * Given an SQL column description this method will return the corresponding + * code for the writer. If the include specials flag is set it will also + * return codes for special columns. Otherwise special columns will be identified + * as specified columns. + * + * @param string $typedescription description + * @param boolean $includeSpecials whether you want to get codes for special columns as well + * + * @return integer $typecode code + */ + public function code( $typedescription, $includeSpecials = false ) { + $r = ((isset($this->sqltype_typeno[$typedescription])) ? $this->sqltype_typeno[$typedescription] : self::C_DATATYPE_SPECIFIED); + if ($includeSpecials) return $r; + if ($r > self::C_DATATYPE_SPECIFIED) return self::C_DATATYPE_SPECIFIED; + return $r; + } + + /** + * This method upgrades the column to the specified data type. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type type / table that needs to be adjusted + * @param string $column column that needs to be altered + * @param integer $datatype target data type + * + * @return void + */ + public function widenColumn( $type, $column, $datatype ) { + $table = $type; + $type = $datatype; + $table = $this->safeTable($table); + $column = $this->safeColumn($column); + $newtype = array_key_exists($type, $this->typeno_sqltype) ? $this->typeno_sqltype[$type] : ''; + $changecolumnSQL = "ALTER TABLE $table CHANGE $column $column $newtype "; + $this->adapter->exec( $changecolumnSQL ); + } + + /** + * Adds a Unique index constrain to the table. + * + * @param string $table table + * @param string $col1 column + * @param string $col2 column + * + * @return void + */ + public function addUniqueIndex( $table,$columns ) { + $table = $this->safeTable($table); + sort($columns); //else we get multiple indexes due to order-effects + foreach($columns as $k=>$v) { + $columns[$k]= $this->safeColumn($v); + } + $r = $this->adapter->get("SHOW INDEX FROM $table"); + $name = 'UQ_'.sha1(implode(',',$columns)); + if ($r) { + foreach($r as $i) { + if ($i['Key_name']== $name) { + return; + } + } + } + $sql = "ALTER IGNORE TABLE $table + ADD UNIQUE INDEX $name (".implode(',',$columns).")"; + $this->adapter->exec($sql); + } + + /** + * This method should add an index to a type and field with name + * $name. + * This methods accepts a type and infers the corresponding table name. + * + * @param $type type to add index to + * @param $name name of the new index + * @param $column field to index + * + * @return void + */ + public function addIndex($type, $name, $column) { + $table = $type; + $table = $this->safeTable($table); + $name = preg_replace('/\W/','',$name); + $column = $this->safeColumn($column); + foreach( $this->adapter->get("SHOW INDEX FROM $table ") as $ind) { + if ($ind['Key_name']===$name) return; + } + try{ $this->adapter->exec("CREATE INDEX $name ON $table ($column) "); }catch(Exception $e){} + } + + /** + * Tests whether a given SQL state is in the list of states. + * + * @param string $state code + * @param array $list array of sql states + * + * @return boolean $yesno occurs in list + */ + public function sqlStateIn($state, $list) { + $stateMap = array( + '42S02'=>RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_TABLE, + '42S22'=>RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, + '23000'=>RedBean_QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION + ); + return in_array((isset($stateMap[$state]) ? $stateMap[$state] : '0'),$list); + } + + /** + * Add the constraints for a specific database driver: MySQL. + * @todo Too many arguments; find a way to solve this in a neater way. + * + * @param string $table table + * @param string $table1 table1 + * @param string $table2 table2 + * @param string $property1 property1 + * @param string $property2 property2 + * + * @return boolean $succes whether the constraint has been applied + */ + protected function constrain($table, $table1, $table2, $property1, $property2) { + try{ + $db = $this->adapter->getCell('select database()'); + $fks = $this->adapter->getCell(" + SELECT count(*) + FROM information_schema.KEY_COLUMN_USAGE + WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND + CONSTRAINT_NAME <>'PRIMARY' AND REFERENCED_TABLE_NAME is not null + ",array($db,$table)); + //already foreign keys added in this association table + if ($fks>0) return false; + $columns = $this->getColumns($table); + if ($this->code($columns[$property1])!==RedBean_QueryWriter_MySQL::C_DATATYPE_UINT32) { + $this->widenColumn($table, $property1, RedBean_QueryWriter_MySQL::C_DATATYPE_UINT32); + } + if ($this->code($columns[$property2])!==RedBean_QueryWriter_MySQL::C_DATATYPE_UINT32) { + $this->widenColumn($table, $property2, RedBean_QueryWriter_MySQL::C_DATATYPE_UINT32); + } + + $sql = " + ALTER TABLE ".$this->noKW($table)." + ADD FOREIGN KEY($property1) references `$table1`(id) ON DELETE CASCADE; + "; + $this->adapter->exec( $sql ); + $sql =" + ALTER TABLE ".$this->noKW($table)." + ADD FOREIGN KEY($property2) references `$table2`(id) ON DELETE CASCADE + "; + $this->adapter->exec( $sql ); + return true; + } catch(Exception $e){ return false; } + } + + /** + * Drops all tables in database + */ + public function wipeAll() { + $this->adapter->exec('SET FOREIGN_KEY_CHECKS=0;'); + foreach($this->getTables() as $t) { + try{ + $this->adapter->exec("drop table if exists`$t`"); + } + catch(Exception $e){} + try{ + $this->adapter->exec("drop view if exists`$t`"); + } + catch(Exception $e){} + } + $this->adapter->exec('SET FOREIGN_KEY_CHECKS=1;'); + } + + +} + + +/** + * RedBean SQLiteWriter with support for SQLite types + * + * @file RedBean/QueryWriter/SQLiteT.php + * @description Represents a SQLite Database to RedBean + * To write a driver for a different database for RedBean + * you should only have to change this file. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_QueryWriter_SQLiteT extends RedBean_QueryWriter_AQueryWriter implements RedBean_QueryWriter { + /** + * + * @var RedBean_Adapter_DBAdapter + * Holds database adapter + */ + protected $adapter; + + /** + * @var string + * character to escape keyword table/column names + */ + protected $quoteCharacter = '`'; + + /** + * Here we describe the datatypes that RedBean + * Uses internally. If you write a QueryWriter for + * RedBean you should provide a list of types like this. + */ + + /** + * DATA TYPE + * Integer Data type + * @var integer + */ + const C_DATATYPE_INTEGER = 0; + + /** + * DATA TYPE + * Numeric Data type (for REAL and date/time) + * @var integer + */ + const C_DATATYPE_NUMERIC = 1; + + /** + * DATA TYPE + * Text type + * @var integer + */ + const C_DATATYPE_TEXT = 2; + + /** + * DATA TYPE + * Specified. This means the developer or DBA + * has altered the column to a different type not + * recognized by RedBean. This high number makes sure + * it will not be converted back to another type by accident. + * @var integer + */ + const C_DATATYPE_SPECIFIED = 99; + + /** + * Constructor + * The Query Writer Constructor also sets up the database + * + * @param RedBean_Adapter_DBAdapter $adapter adapter + */ + public function __construct( RedBean_Adapter $adapter ) { + + $this->typeno_sqltype = array( + RedBean_QueryWriter_SQLiteT::C_DATATYPE_INTEGER=>'INTEGER', + RedBean_QueryWriter_SQLiteT::C_DATATYPE_NUMERIC=>'NUMERIC', + RedBean_QueryWriter_SQLiteT::C_DATATYPE_TEXT=>'TEXT', + ); + + $this->sqltype_typeno = array(); + foreach($this->typeno_sqltype as $k=>$v) + $this->sqltype_typeno[$v]=$k; + + + $this->adapter = $adapter; + parent::__construct($adapter); + } + + /** + * This method returns the datatype to be used for primary key IDS and + * foreign keys. Returns one if the data type constants. + * + * @return integer $const data type to be used for IDS. + */ + public function getTypeForID() { + return self::C_DATATYPE_INTEGER; + } + + /** + * Returns the MySQL Column Type Code (integer) that corresponds + * to the given value type. + * + * @param string $value value + * + * @return integer $type type + */ + public function scanType( $value, $flagSpecial=false ) { + $this->svalue=$value; + if ($value===false) return self::C_DATATYPE_INTEGER; + if ($value===null) return self::C_DATATYPE_INTEGER; //for fks + if ($this->startsWithZeros($value)) return self::C_DATATYPE_TEXT; + if (is_numeric($value) && (intval($value)==$value) && $value<2147483648) return self::C_DATATYPE_INTEGER; + if ((is_numeric($value) && $value < 2147483648) + || preg_match('/\d{4}\-\d\d\-\d\d/',$value) + || preg_match('/\d{4}\-\d\d\-\d\d\s\d\d:\d\d:\d\d/',$value) + ) { + return self::C_DATATYPE_NUMERIC; + } + return self::C_DATATYPE_TEXT; + } + + /** + * Adds a column of a given type to a table + * + * @param string $table table + * @param string $column column + * @param integer $type type + */ + public function addColumn( $table, $column, $type) { + $column = $this->check($column); + $table = $this->check($table); + $type=$this->typeno_sqltype[$type]; + $sql = "ALTER TABLE `$table` ADD `$column` $type "; + $this->adapter->exec( $sql ); + } + + /** + * Returns the Type Code for a Column Description. + * Given an SQL column description this method will return the corresponding + * code for the writer. If the include specials flag is set it will also + * return codes for special columns. Otherwise special columns will be identified + * as specified columns. + * + * @param string $typedescription description + * @param boolean $includeSpecials whether you want to get codes for special columns as well + * + * @return integer $typecode code + */ + public function code( $typedescription, $includeSpecials = false ) { + $r = ((isset($this->sqltype_typeno[$typedescription])) ? $this->sqltype_typeno[$typedescription] : 99); + if ($includeSpecials) return $r; + if ($r > self::C_DATATYPE_SPECIFIED) return self::C_DATATYPE_SPECIFIED; + return $r; + } + + + + + /** + * Gets all information about a table (from a type). + * + * Format: + * array( + * name => name of the table + * columns => array( name => datatype ) + * indexes => array() raw index information rows from PRAGMA query + * keys => array() raw key information rows from PRAGMA query + * ) + * + * @param string $type type you want to get info of + * + * @return array $info + */ + protected function getTable($type) { + $tableName = $this->safeTable($type,true); + $columns = $this->getColumns($type); + $indexes = $this->getIndexes($type); + $keys = $this->getKeys($type); + $table = array('columns'=>$columns,'indexes'=>$indexes,'keys'=>$keys,'name'=>$tableName); + $this->tableArchive[$tableName] = $table; + return $table; + } + + /** + * Puts a table. Updates the table structure. + * In SQLite we can't change columns, drop columns, change or add foreign keys so we + * have a table-rebuild function. You simply load your table with getTable(), modify it and + * then store it with putTable()... + * + * @param array $tableMap information array + */ + protected function putTable($tableMap) { + $table = $tableMap['name']; + $q = array(); + $q[] = "DROP TABLE IF EXISTS tmp_backup;"; + $oldColumnNames = array_keys($this->getColumns($table)); + foreach($oldColumnNames as $k=>$v) $oldColumnNames[$k] = "`$v`"; + $q[] = "CREATE TEMPORARY TABLE tmp_backup(".implode(",",$oldColumnNames).");"; + $q[] = "INSERT INTO tmp_backup SELECT * FROM `$table`;"; + $q[] = "PRAGMA foreign_keys = 0 "; + $q[] = "DROP TABLE `$table`;"; + $newTableDefStr = ''; + foreach($tableMap['columns'] as $column=>$type) { + if ($column != 'id') { + $newTableDefStr .= ",`$column` $type"; + } + } + $fkDef = ''; + foreach($tableMap['keys'] as $key) { + $fkDef .= ", FOREIGN KEY(`{$key['from']}`) + REFERENCES `{$key['table']}`(`{$key['to']}`) + ON DELETE {$key['on_delete']} ON UPDATE {$key['on_update']}"; + } + $q[] = "CREATE TABLE `$table` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT $newTableDefStr $fkDef );"; + foreach($tableMap['indexes'] as $name=>$index) { + if (strpos($name,'UQ_')===0) { + $cols = explode('__',substr($name,strlen('UQ_'.$table))); + foreach($cols as $k=>$v) $cols[$k] = "`$v`"; + $q[] = "CREATE UNIQUE INDEX $name ON `$table` (".implode(',',$cols).")"; + } + else $q[] = "CREATE INDEX $name ON `$table` ({$index['name']}) "; + } + $q[] = "INSERT INTO `$table` SELECT * FROM tmp_backup;"; + $q[] = "DROP TABLE tmp_backup;"; + $q[] = "PRAGMA foreign_keys = 1 "; + foreach($q as $sq) $this->adapter->exec($sq); + + } + + /** + * This method upgrades the column to the specified data type. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type type / table that needs to be adjusted + * @param string $column column that needs to be altered + * @param integer $datatype target data type + * + * @return void + */ + public function widenColumn( $type, $column, $datatype ) { + $t = $this->getTable($type); + $t['columns'][$column] = $this->typeno_sqltype[$datatype]; + $this->putTable($t); + } + + + /** + * Returns all tables in the database + * + * @return array $tables tables + */ + public function getTables() { + return $this->adapter->getCol( "SELECT name FROM sqlite_master + WHERE type='table' AND name!='sqlite_sequence';" ); + } + + /** + * Creates an empty, column-less table for a bean. + * + * @param string $table table + */ + public function createTable( $table ) { + $table = $this->safeTable($table); + $sql = "CREATE TABLE $table ( id INTEGER PRIMARY KEY AUTOINCREMENT ) "; + $this->adapter->exec( $sql ); + } + + /** + * Returns an array containing the column names of the specified table. + * + * @param string $table table + * + * @return array $columns columns + */ + public function getColumns( $table ) { + $table = $this->safeTable($table, true); + $columnsRaw = $this->adapter->get("PRAGMA table_info('$table')"); + $columns = array(); + foreach($columnsRaw as $r) { + $columns[$r['name']]=$r['type']; + } + return $columns; + } + + /** + * Returns the indexes for type $type. + * + * @param string $type + * + * @return array $indexInfo index information + */ + protected function getIndexes($type) { + $table = $this->safeTable($type, true); + $indexes = $this->adapter->get("PRAGMA index_list('$table')"); + $indexInfoList = array(); + foreach($indexes as $i) { + $indexInfoList[$i['name']] = $this->adapter->getRow("PRAGMA index_info('{$i['name']}') "); + $indexInfoList[$i['name']]['unique'] = $i['unique']; + } + return $indexInfoList; + } + + /** + * Returns the keys for type $type. + * + * @param string $type + * + * @return array $keysInfo keys information + */ + protected function getKeys($type) { + $table = $this->safeTable($type,true); + $keys = $this->adapter->get("PRAGMA foreign_key_list('$table')"); + $keyInfoList = array(); + foreach($keys as $k) { + $keyInfoList['from_'.$k['from'].'_to_table_'.$k['table'].'_col_'.$k['to']] = $k; + } + return $keyInfoList; + } + + /** + * Adds a Unique index constrain to the table. + * + * @param string $table table + * @param string $column1 first column + * @param string $column2 second column + * + * @return void + */ + public function addUniqueIndex( $type,$columns ) { + $table = $this->safeTable($type,true); + $name = 'UQ_'.$table.implode('__',$columns); + $t = $this->getTable($type); + if (isset($t['indexes'][$name])) return; + $t['indexes'][$name] = array('name'=>$name); + $this->putTable($t); + } + + /** + * Given an Database Specific SQLState and a list of QueryWriter + * Standard SQL States this function converts the raw SQL state to a + * database agnostic ANSI-92 SQL states and checks if the given state + * is in the list of agnostic states. + * + * @param string $state state + * @param array $list list of states + * + * @return boolean $isInArray whether state is in list + */ + public function sqlStateIn($state, $list) { + $stateMap = array( + 'HY000'=>RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_TABLE, + '23000'=>RedBean_QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION + ); + return in_array((isset($stateMap[$state]) ? $stateMap[$state] : '0'),$list); + } + + /** + * This method should add an index to a type and field with name + * $name. + * This methods accepts a type and infers the corresponding table name. + * + * @param $type type to add index to + * @param $name name of the new index + * @param $column field to index + * + * @return void + */ + public function addIndex($type, $name, $column) { + $table = $type; + $table = $this->safeTable($table); + $name = preg_replace('/\W/','',$name); + $column = $this->safeColumn($column,true); + foreach( $this->adapter->get("PRAGMA INDEX_LIST($table) ") as $ind) { + if ($ind['name']===$name) return; + } + $t = $this->getTable($type); + $t['indexes'][$name] = array('name'=>$column); + return $this->putTable($t); + } + + + /** + * Counts rows in a table. + * Uses SQLite optimization for deleting all records (i.e. no WHERE) + * + * @param string $beanType + * + * @return integer $numRowsFound + */ + public function wipe($type) { + $table = $this->safeTable($type); + $this->adapter->exec("DELETE FROM $table"); + } + + /** + * Adds a foreign key to a type + * + * @param string $type type you want to modify table of + * @param string $targetType target type + * @param string $field field of the type that needs to get the fk + * @param string $targetField field where the fk needs to point to + * @param boolean $isDep whether this field is dependent on it's referenced record + * + * @return bool $success whether an FK has been added + */ + public function addFK( $type, $targetType, $field, $targetField, $isDep=false) { + return $this->buildFK($type, $targetType, $field, $targetField, $isDep); + } + + /** + * Adds a foreign key to a type + * + * @param string $type type you want to modify table of + * @param string $targetType target type + * @param string $field field of the type that needs to get the fk + * @param string $targetField field where the fk needs to point to + * @param integer $buildopt 0 = NO ACTION, 1 = ON DELETE CASCADE + * + * @return boolean $didIt + * + * @note: cant put this in try-catch because that can hide the fact + * that database has been damaged. + */ + + protected function buildFK($type, $targetType, $field, $targetField,$constraint=false) { + $consSQL = ($constraint ? 'CASCADE' : 'SET NULL'); + $t = $this->getTable($type); + $label = 'from_'.$field.'_to_table_'.$targetType.'_col_'.$targetField; + if (isset($t['keys'][$label]) + && $t['keys'][$label]['table']===$targetType + && $t['keys'][$label]['from']===$field + && $t['keys'][$label]['to']===$targetField + && $t['keys'][$label]['on_delete']===$consSQL + ) return false; + + $t['keys'][$label] = array( + 'table' => $targetType, + 'from' => $field, + 'to' => $targetField, + 'on_update' => 'SET NULL', + 'on_delete' => $consSQL + ); + $this->putTable($t); + return true; + } + + + /** + * Add the constraints for a specific database driver: SQLite. + * @todo Too many arguments; find a way to solve this in a neater way. + * + * @param string $table table + * @param string $table1 table1 + * @param string $table2 table2 + * @param string $property1 property1 + * @param string $property2 property2 + * + * @return boolean $succes whether the constraint has been applied + */ + protected function constrain($table, $table1, $table2, $property1, $property2) { + $writer = $this; + $adapter = $this->adapter; + $firstState = $this->buildFK($table,$table1,$property1,'id',true); + $secondState = $this->buildFK($table,$table2,$property2,'id',true); + return ($firstState && $secondState); + } + + /** + * Removes all tables and views from the database. + * + * @return void + */ + public function wipeAll() { + $this->adapter->exec('PRAGMA foreign_keys = 0 '); + foreach($this->getTables() as $t) { + try{ + $this->adapter->exec("drop table if exists`$t`"); + } + catch(Exception $e){} + try{ + $this->adapter->exec("drop view if exists`$t`"); + } + catch(Exception $e){} + } + $this->adapter->exec('PRAGMA foreign_keys = 1 '); + } + + +} + + +/** + * RedBean PostgreSQL Query Writer + * + * @file RedBean/QueryWriter/PostgreSQL.php + * @description QueryWriter for the PostgreSQL database system. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_QueryWriter_PostgreSQL extends RedBean_QueryWriter_AQueryWriter implements RedBean_QueryWriter { + + /** + * DATA TYPE + * Integer Data Type + * @var integer + */ + const C_DATATYPE_INTEGER = 0; + + /** + * DATA TYPE + * Double Precision Type + * @var integer + */ + const C_DATATYPE_DOUBLE = 1; + + /** + * DATA TYPE + * String Data Type + * @var integer + */ + const C_DATATYPE_TEXT = 3; + + + /** + * Special type date for storing date values: YYYY-MM-DD + * @var integer + */ + const C_DATATYPE_SPECIAL_DATE = 80; + + /** + * Special type date for storing date values: YYYY-MM-DD HH:MM:SS + * @var integer + */ + const C_DATATYPE_SPECIAL_DATETIME = 81; + + + + /** + * Specified field type cannot be overruled + * @var integer + */ + const C_DATATYPE_SPECIFIED = 99; + + + /** + * Holds Database Adapter + * @var RedBean_DBAdapter + */ + protected $adapter; + + /** + * character to escape keyword table/column names + * @var string + */ + protected $quoteCharacter = '"'; + + /** + * Default Value + * @var string + */ + protected $defaultValue = 'DEFAULT'; + + /** + * This method returns the datatype to be used for primary key IDS and + * foreign keys. Returns one if the data type constants. + * + * @return integer $const data type to be used for IDS. + */ + public function getTypeForID() { + return self::C_DATATYPE_INTEGER; + } + + /** + * Returns the insert suffix SQL Snippet + * + * @param string $table table + * + * @return string $sql SQL Snippet + */ + protected function getInsertSuffix($table) { + return 'RETURNING id '; + } + + /** + * Constructor + * The Query Writer Constructor also sets up the database + * + * @param RedBean_DBAdapter $adapter adapter + */ + public function __construct( RedBean_Adapter $adapter ) { + + + $this->typeno_sqltype = array( + self::C_DATATYPE_INTEGER=>' integer ', + self::C_DATATYPE_DOUBLE=>' double precision ', + self::C_DATATYPE_TEXT=>' text ', + self::C_DATATYPE_SPECIAL_DATE => ' date ', + self::C_DATATYPE_SPECIAL_DATETIME => ' timestamp without time zone ', + + + ); + + $this->sqltype_typeno = array(); + foreach($this->typeno_sqltype as $k=>$v) + $this->sqltype_typeno[trim(strtolower($v))]=$k; + + + $this->adapter = $adapter; + parent::__construct(); + } + + /** + * Returns all tables in the database + * + * @return array $tables tables + */ + public function getTables() { + return $this->adapter->getCol( "select table_name from information_schema.tables + where table_schema = 'public'" ); + } + + /** + * Creates an empty, column-less table for a bean. + * + * @param string $table table to create + */ + public function createTable( $table ) { + $table = $this->safeTable($table); + $sql = " CREATE TABLE $table (id SERIAL PRIMARY KEY); "; + $this->adapter->exec( $sql ); + } + + /** + * Returns an array containing the column names of the specified table. + * + * @param string $table table to get columns from + * + * @return array $columns array filled with column (name=>type) + */ + public function getColumns( $table ) { + $table = $this->safeTable($table, true); + $columnsRaw = $this->adapter->get("select column_name, data_type from information_schema.columns where table_name='$table'"); + foreach($columnsRaw as $r) { + $columns[$r['column_name']]=$r['data_type']; + } + return $columns; + } + + /** + * Returns the pgSQL Column Type Code (integer) that corresponds + * to the given value type. + * + * @param string $value value to determine type of + * + * @return integer $type type code for this value + */ + public function scanType( $value, $flagSpecial=false ) { + + $this->svalue=$value; + + if ($flagSpecial && $value) { + if (preg_match('/^\d{4}\-\d\d-\d\d$/',$value)) { + return RedBean_QueryWriter_PostgreSQL::C_DATATYPE_SPECIAL_DATE; + } + if (preg_match('/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d(\.\d{1,6})?$/',$value)) { + return RedBean_QueryWriter_PostgreSQL::C_DATATYPE_SPECIAL_DATETIME; + } + + } + + $sz = ($this->startsWithZeros($value)); + if ($sz) return self::C_DATATYPE_TEXT; + if ($value===null || ($value instanceof RedBean_Driver_PDO_NULL) ||(is_numeric($value) + && floor($value)==$value + && $value < 2147483648 + && $value > -2147483648)) { + return self::C_DATATYPE_INTEGER; + } + elseif(is_numeric($value)) { + return self::C_DATATYPE_DOUBLE; + } + else { + return self::C_DATATYPE_TEXT; + } + } + + /** + * Returns the Type Code for a Column Description. + * Given an SQL column description this method will return the corresponding + * code for the writer. If the include specials flag is set it will also + * return codes for special columns. Otherwise special columns will be identified + * as specified columns. + * + * @param string $typedescription description + * @param boolean $includeSpecials whether you want to get codes for special columns as well + * + * @return integer $typecode code + */ + public function code( $typedescription, $includeSpecials = false ) { + $r = ((isset($this->sqltype_typeno[$typedescription])) ? $this->sqltype_typeno[$typedescription] : 99); + if ($includeSpecials) return $r; + if ($r > self::C_DATATYPE_SPECIFIED) return self::C_DATATYPE_SPECIFIED; + return $r; + } + + /** + * This method upgrades the column to the specified data type. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type type / table that needs to be adjusted + * @param string $column column that needs to be altered + * @param integer $datatype target data type + * + * @return void + */ + public function widenColumn( $type, $column, $datatype ) { + $table = $type; + $type = $datatype; + $table = $this->safeTable($table); + $column = $this->safeColumn($column); + $newtype = $this->typeno_sqltype[$type]; + $changecolumnSQL = "ALTER TABLE $table \n\t ALTER COLUMN $column TYPE $newtype "; + $this->adapter->exec( $changecolumnSQL ); + } + + /** + * Adds a Unique index constrain to the table. + * + * @param string $table table to add index to + * @param string $col1 column to be part of index + * @param string $col2 column 2 to be part of index + * + * @return void + */ + public function addUniqueIndex( $table,$columns ) { + $table = $this->safeTable($table, true); + sort($columns); //else we get multiple indexes due to order-effects + foreach($columns as $k=>$v) { + $columns[$k]=$this->safeColumn($v); + } + $r = $this->adapter->get("SELECT + i.relname as index_name + FROM + pg_class t, + pg_class i, + pg_index ix, + pg_attribute a + WHERE + t.oid = ix.indrelid + AND i.oid = ix.indexrelid + AND a.attrelid = t.oid + AND a.attnum = ANY(ix.indkey) + AND t.relkind = 'r' + AND t.relname = '$table' + ORDER BY t.relname, i.relname;"); + + $name = "UQ_".sha1($table.implode(',',$columns)); + if ($r) { + foreach($r as $i) { + if (strtolower( $i['index_name'] )== strtolower( $name )) { + return; + } + } + } + $sql = "ALTER TABLE \"$table\" + ADD CONSTRAINT $name UNIQUE (".implode(',',$columns).")"; + $this->adapter->exec($sql); + } + + /** + * Given an Database Specific SQLState and a list of QueryWriter + * Standard SQL States this function converts the raw SQL state to a + * database agnostic ANSI-92 SQL states and checks if the given state + * is in the list of agnostic states. + * + * @param string $state state + * @param array $list list of states + * + * @return boolean $isInArray whether state is in list + */ + public function sqlStateIn($state, $list) { + $stateMap = array( + '42P01'=>RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_TABLE, + '42703'=>RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, + '23505'=>RedBean_QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION + ); + return in_array((isset($stateMap[$state]) ? $stateMap[$state] : '0'),$list); + } + + + /** + * This method should add an index to a type and field with name + * $name. + * This methods accepts a type and infers the corresponding table name. + * + * @param $type type to add index to + * @param $name name of the new index + * @param $column field to index + * + * @return void + */ + public function addIndex($type, $name, $column) { + $table = $type; + $table = $this->safeTable($table); + $name = preg_replace('/\W/','',$name); + $column = $this->safeColumn($column); + if ($this->adapter->getCell("SELECT COUNT(*) FROM pg_class WHERE relname = '$name'")) return; + try{ $this->adapter->exec("CREATE INDEX $name ON $table ($column) "); }catch(Exception $e){} + } + + /** + * Adds a foreign key to a table. The foreign key will not have any action; you + * may configure this afterwards. + * + * @param string $type type you want to modify table of + * @param string $targetType target type + * @param string $field field of the type that needs to get the fk + * @param string $targetField field where the fk needs to point to + * + * @return bool $success whether an FK has been added + */ + public function addFK( $type, $targetType, $field, $targetField, $isDep = false) { + try{ + $table = $this->safeTable($type); + $column = $this->safeColumn($field); + $tableNoQ = $this->safeTable($type,true); + $columnNoQ = $this->safeColumn($field,true); + $targetTable = $this->safeTable($targetType); + $targetTableNoQ = $this->safeTable($targetType,true); + $targetColumn = $this->safeColumn($targetField); + $targetColumnNoQ = $this->safeColumn($targetField,true); + + + $sql = "SELECT + tc.constraint_name, + tc.table_name, + kcu.column_name, + ccu.table_name AS foreign_table_name, + ccu.column_name AS foreign_column_name, + rc.delete_rule + FROM + information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name + JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name + JOIN information_schema.referential_constraints AS rc ON ccu.constraint_name = rc.constraint_name + WHERE constraint_type = 'FOREIGN KEY' AND tc.table_catalog=current_database() + AND tc.table_name = '$tableNoQ' + AND ccu.table_name = '$targetTableNoQ' + AND kcu.column_name = '$columnNoQ' + AND ccu.column_name = '$targetColumnNoQ' + "; + + + $row = $this->adapter->getRow($sql); + + $flagAddKey = false; + + if (!$row) $flagAddKey = true; + + if ($row) { + if (($row['delete_rule']=='SET NULL' && $isDep) || + ($row['delete_rule']!='SET NULL' && !$isDep)) { + //delete old key + $flagAddKey = true; //and order a new one + $cName = $row['constraint_name']; + $sql = "ALTER TABLE $table DROP CONSTRAINT $cName "; + $this->adapter->exec($sql); + } + + } + + if ($flagAddKey) { + $delRule = ($isDep ? 'CASCADE' : 'SET NULL'); + $this->adapter->exec("ALTER TABLE $table + ADD FOREIGN KEY ( $column ) REFERENCES $targetTable ( + $targetColumn) ON DELETE $delRule ON UPDATE SET NULL DEFERRABLE ;"); + return true; + } + return false; + + } + catch(Exception $e){ return false; } + } + + + + /** + * Add the constraints for a specific database driver: PostgreSQL. + * @todo Too many arguments; find a way to solve this in a neater way. + * + * @param string $table table + * @param string $table1 table1 + * @param string $table2 table2 + * @param string $property1 property1 + * @param string $property2 property2 + * + * @return boolean $succes whether the constraint has been applied + */ + protected function constrain($table, $table1, $table2, $property1, $property2) { + try{ + $writer = $this; + $adapter = $this->adapter; + $fkCode = 'fk'.md5($table.$property1.$property2); + $sql = " + SELECT + c.oid, + n.nspname, + c.relname, + n2.nspname, + c2.relname, + cons.conname + FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + LEFT OUTER JOIN pg_constraint cons ON cons.conrelid = c.oid + LEFT OUTER JOIN pg_class c2 ON cons.confrelid = c2.oid + LEFT OUTER JOIN pg_namespace n2 ON n2.oid = c2.relnamespace + WHERE c.relkind = 'r' + AND n.nspname IN ('public') + AND (cons.contype = 'f' OR cons.contype IS NULL) + AND + ( cons.conname = '{$fkCode}a' OR cons.conname = '{$fkCode}b' ) + + "; + + $rows = $adapter->get( $sql ); + if (!count($rows)) { + $sql1 = "ALTER TABLE \"$table\" ADD CONSTRAINT + {$fkCode}a FOREIGN KEY ($property1) + REFERENCES \"$table1\" (id) ON DELETE CASCADE "; + $sql2 = "ALTER TABLE \"$table\" ADD CONSTRAINT + {$fkCode}b FOREIGN KEY ($property2) + REFERENCES \"$table2\" (id) ON DELETE CASCADE "; + $adapter->exec($sql1); + $adapter->exec($sql2); + } + return true; + } + catch(Exception $e){ return false; } + } + + /** + * Removes all tables and views from the database. + */ + public function wipeAll() { + $this->adapter->exec('SET CONSTRAINTS ALL DEFERRED'); + foreach($this->getTables() as $t) { + $t = $this->noKW($t); + try{ + $this->adapter->exec("drop table if exists $t CASCADE "); + } + catch(Exception $e){ } + try{ + $this->adapter->exec("drop view if exists $t CASCADE "); + } + catch(Exception $e){ throw $e; } + } + $this->adapter->exec('SET CONSTRAINTS ALL IMMEDIATE'); + } + + + + /** + * This method removes all beans of a certain type. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type bean type + * + * @return void + */ + public function wipe($type) { + $table = $type; + $table = $this->safeTable($table); + $sql = "TRUNCATE $table CASCADE"; + $this->adapter->exec($sql); + } + +} + + +/** + * RedBean CUBRID Writer + * + * @file RedBean/QueryWriter/CUBRID.php + * @description Represents a CUBRID Database to RedBean + * To write a driver for a different database for RedBean + * you should only have to change this file. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + + */ +class RedBean_QueryWriter_CUBRID extends RedBean_QueryWriter_AQueryWriter implements RedBean_QueryWriter { + + + /** + * Here we describe the datatypes that RedBean + * Uses internally. If you write a QueryWriter for + * RedBean you should provide a list of types like this. + */ + + /** + * + * DATA TYPE + * Signed 4 byte Integer + * @var integer + */ + const C_DATATYPE_INTEGER = 0; + + /** + * DATA TYPE + * Double precision floating point number + * @var integer + */ + const C_DATATYPE_DOUBLE = 1; + + /** + * + * DATA TYPE + * Variable length text + * @var integer + */ + const C_DATATYPE_STRING = 2; + + + /** + * Special type date for storing date values: YYYY-MM-DD + * @var integer + */ + const C_DATATYPE_SPECIAL_DATE = 80; + + /** + * Special type datetime for store date-time values: YYYY-MM-DD HH:II:SS + * @var integer + */ + const C_DATATYPE_SPECIAL_DATETIME = 81; + + + /** + * + * DATA TYPE + * Specified. This means the developer or DBA + * has altered the column to a different type not + * recognized by RedBean. This high number makes sure + * it will not be converted back to another type by accident. + * @var integer + */ + const C_DATATYPE_SPECIFIED = 99; + + + + /** + * Holds the RedBean Database Adapter. + * @var RedBean_Adapter_DBAdapter + */ + protected $adapter; + + /** + * character to escape keyword table/column names + * @var string + */ + protected $quoteCharacter = '`'; + + /** + * Do everything that needs to be done to format a table name. + * + * @param string $name of table + * + * @return string table name + */ + public function safeTable($name, $noQuotes = false) { + $name = strtolower($name); + $name = $this->check($name); + if (!$noQuotes) $name = $this->noKW($name); + return $name; + } + + + /** + * Do everything that needs to be done to format a column name. + * + * @param string $name of column + * + * @return string $column name + */ + public function safeColumn($name, $noQuotes = false) { + $name = strtolower($name); + $name = $this->check($name); + if (!$noQuotes) $name = $this->noKW($name); + return $name; + } + + /** + * Constructor. + * The Query Writer Constructor also sets up the database. + * + * @param RedBean_Adapter_DBAdapter $adapter adapter + * + */ + public function __construct( RedBean_Adapter $adapter ) { + + $this->typeno_sqltype = array( + RedBean_QueryWriter_CUBRID::C_DATATYPE_INTEGER => ' INTEGER ', + RedBean_QueryWriter_CUBRID::C_DATATYPE_DOUBLE => ' DOUBLE ', + RedBean_QueryWriter_CUBRID::C_DATATYPE_STRING => ' STRING ', + RedBean_QueryWriter_CUBRID::C_DATATYPE_SPECIAL_DATE => ' DATE ', + RedBean_QueryWriter_CUBRID::C_DATATYPE_SPECIAL_DATETIME => ' DATETIME ', + ); + + $this->sqltype_typeno = array(); + foreach($this->typeno_sqltype as $k=>$v) + $this->sqltype_typeno[trim(($v))]=$k; + $this->sqltype_typeno['STRING(1073741823)'] = self::C_DATATYPE_STRING; + + $this->adapter = $adapter; + parent::__construct(); + } + + /** + * This method returns the datatype to be used for primary key IDS and + * foreign keys. Returns one if the data type constants. + * + * @return integer $const data type to be used for IDS. + */ + public function getTypeForID() { + return self::C_DATATYPE_INTEGER; + } + + /** + * Returns all tables in the database. + * + * @return array $tables tables + */ + public function getTables() { + $rows = $this->adapter->getCol( "SELECT class_name FROM db_class WHERE is_system_class = 'NO';" ); + return $rows; + } + + /** + * Creates an empty, column-less table for a bean based on it's type. + * This function creates an empty table for a bean. It uses the + * safeTable() function to convert the type name to a table name. + * + * @param string $table type of bean you want to create a table for + * + * @return void + */ + public function createTable( $table ) { + $rawTable = $this->safeTable($table,true); + $table = $this->safeTable($table); + + $sql = ' CREATE TABLE '.$table.' ( + "id" integer AUTO_INCREMENT, + CONSTRAINT "pk_'.$rawTable.'_id" PRIMARY KEY("id") + )'; + $this->adapter->exec( $sql ); + } + + + + /** + * Returns an array containing the column names of the specified table. + * + * @param string $table table + * + * @return array $columns columns + */ + public function getColumns( $table ) { + $columns = array(); + $table = $this->safeTable($table); + $columnsRaw = $this->adapter->get("SHOW COLUMNS FROM $table"); + foreach($columnsRaw as $r) { + $columns[$r['Field']]=$r['Type']; + } + return $columns; + } + + /** + * Returns the Column Type Code (integer) that corresponds + * to the given value type. + * + * @param string $value value + * + * @return integer $type type + */ + public function scanType( $value, $flagSpecial=false ) { + $this->svalue = $value; + + if (is_null($value)) { + return self::C_DATATYPE_INTEGER; + } + + if ($flagSpecial) { + if (preg_match('/^\d{4}\-\d\d-\d\d$/',$value)) { + return self::C_DATATYPE_SPECIAL_DATE; + } + if (preg_match('/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d$/',$value)) { + return self::C_DATATYPE_SPECIAL_DATETIME; + } + } + $value = strval($value); + if (!$this->startsWithZeros($value)) { + + if (is_numeric($value) && (floor($value)==$value) && $value >= -2147483647 && $value <= 2147483647 ) { + return self::C_DATATYPE_INTEGER; + } + if (is_numeric($value)) { + return self::C_DATATYPE_DOUBLE; + } + } + + return self::C_DATATYPE_STRING; + } + + /** + * Returns the Type Code for a Column Description. + * Given an SQL column description this method will return the corresponding + * code for the writer. If the include specials flag is set it will also + * return codes for special columns. Otherwise special columns will be identified + * as specified columns. + * + * @param string $typedescription description + * @param boolean $includeSpecials whether you want to get codes for special columns as well + * + * @return integer $typecode code + */ + public function code( $typedescription, $includeSpecials = false ) { + + + $r = ((isset($this->sqltype_typeno[$typedescription])) ? $this->sqltype_typeno[$typedescription] : self::C_DATATYPE_SPECIFIED); + + if ($includeSpecials) return $r; + if ($r > self::C_DATATYPE_SPECIFIED) return self::C_DATATYPE_SPECIFIED; + return $r; + } + + /** + * This method adds a column to a table. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type name of the table + * @param string $column name of the column + * @param integer $field data type for field + * + * @return void + * + */ + public function addColumn( $type, $column, $field ) { + $table = $type; + $type = $field; + $table = $this->safeTable($table); + $column = $this->safeColumn($column); + $type = array_key_exists($type, $this->typeno_sqltype) ? $this->typeno_sqltype[$type] : ''; + $sql = "ALTER TABLE $table ADD COLUMN $column $type "; + $this->adapter->exec( $sql ); + } + + + /** + * This method upgrades the column to the specified data type. + * This methods accepts a type and infers the corresponding table name. + * + * @param string $type type / table that needs to be adjusted + * @param string $column column that needs to be altered + * @param integer $datatype target data type + * + * @return void + */ + public function widenColumn( $type, $column, $datatype ) { + $table = $type; + $type = $datatype; + $table = $this->safeTable($table); + $column = $this->safeColumn($column); + $newtype = array_key_exists($type, $this->typeno_sqltype) ? $this->typeno_sqltype[$type] : ''; + $changecolumnSQL = "ALTER TABLE $table CHANGE $column $column $newtype "; + $this->adapter->exec( $changecolumnSQL ); + } + + /** + * Adds a Unique index constrain to the table. + * + * @param string $table table + * @param string $col1 column + * @param string $col2 column + * + * @return void + */ + public function addUniqueIndex( $table,$columns ) { + $table = $this->safeTable($table); + sort($columns); //else we get multiple indexes due to order-effects + foreach($columns as $k=>$v) { + $columns[$k]= $this->safeColumn($v); + } + $r = $this->adapter->get("SHOW INDEX FROM $table"); + $name = 'UQ_'.sha1(implode(',',$columns)); + if ($r) { + foreach($r as $i) { + if (strtoupper($i['Key_name'])== strtoupper($name)) { + return; + } + } + } + $sql = "ALTER TABLE $table + ADD CONSTRAINT UNIQUE $name (".implode(',',$columns).")"; + $this->adapter->exec($sql); + } + + /** + * Tests whether a given SQL state is in the list of states. + * + * @param string $state code + * @param array $list array of sql states + * + * @return boolean $yesno occurs in list + */ + public function sqlStateIn($state, $list) { + if ($state=='HY000') { + if (in_array(RedBean_QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION,$list)) return true; + if (in_array(RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,$list)) return true; + if (in_array(RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,$list)) return true; + } + return false; + } + + + /** + * Adds a constraint. If one of the beans gets trashed + * the other, related bean should be removed as well. + * + * @param RedBean_OODBBean $bean1 first bean + * @param RedBean_OODBBean $bean2 second bean + * @param bool $dontCache by default we use a cache, TRUE = NO CACHING (optional) + * + * @return void + */ + public function addConstraint( RedBean_OODBBean $bean1, RedBean_OODBBean $bean2) { + $table1 = $bean1->getMeta('type'); + $table2 = $bean2->getMeta('type'); + $writer = $this; + $adapter = $this->adapter; + $table = RedBean_QueryWriter_AQueryWriter::getAssocTableFormat( array( $table1,$table2) ); + $property1 = $bean1->getMeta('type') . '_id'; + $property2 = $bean2->getMeta('type') . '_id'; + if ($property1==$property2) $property2 = $bean2->getMeta('type').'2_id'; + //Dispatch to right method + return $this->constrain($table, $table1, $table2, $property1, $property2); + } + + + /** + * Add the constraints for a specific database driver: CUBRID + * @todo Too many arguments; find a way to solve this in a neater way. + * + * @param string $table table + * @param string $table1 table1 + * @param string $table2 table2 + * @param string $property1 property1 + * @param string $property2 property2 + * + * @return boolean $succes whether the constraint has been applied + */ + protected function constrain($table, $table1, $table2, $property1, $property2) { + $writer = $this; + $adapter = $this->adapter; + $firstState = $this->buildFK($table,$table1,$property1,'id',true); + $secondState = $this->buildFK($table,$table2,$property2,'id',true); + return ($firstState && $secondState); + } + + /** + * This method should add an index to a type and field with name + * $name. + * This methods accepts a type and infers the corresponding table name. + * + * @param $type type to add index to + * @param $name name of the new index + * @param $column field to index + * + * @return void + */ + public function addIndex($type, $name, $column) { + $table = $type; + $table = $this->safeTable($table); + $name = preg_replace('/\W/','',$name); + $column = $this->safeColumn($column); + $index = $this->adapter->getRow("SELECT 1 as `exists` FROM db_index WHERE index_name = ? ",array($name)); + if ($index && $index['exists']) return; // positive number will return, 0 will continue. + try{ $this->adapter->exec("CREATE INDEX $name ON $table ($column) "); }catch(Exception $e){} + } + + /** + * This method adds a foreign key from type and field to + * target type and target field. + * The foreign key is created without an action. On delete/update + * no action will be triggered. The FK is only used to allow database + * tools to generate pretty diagrams and to make it easy to add actions + * later on. + * This methods accepts a type and infers the corresponding table name. + * + * + * @param string $type type that will have a foreign key field + * @param string $targetType points to this type + * @param string $field field that contains the foreign key value + * @param string $targetField field where the fk points to + * + * @return void + */ + public function addFK( $type, $targetType, $field, $targetField, $isDependent = false) { + return $this->buildFK($type, $targetType, $field, $targetField, $isDependent); + } + + + /** + * This method adds a foreign key from type and field to + * target type and target field. + * The foreign key is created without an action. On delete/update + * no action will be triggered. The FK is only used to allow database + * tools to generate pretty diagrams and to make it easy to add actions + * later on. + * This methods accepts a type and infers the corresponding table name. + * + * + * @param string $type type that will have a foreign key field + * @param string $targetType points to this type + * @param string $field field that contains the foreign key value + * @param string $targetField field where the fk points to + * + * @return void + */ + protected function buildFK($type, $targetType, $field, $targetField,$isDep=false) { + $table = $this->safeTable($type); + $tableNoQ = $this->safeTable($type,true); + $targetTable = $this->safeTable($targetType); + $targetTableNoQ = $this->safeTable($targetType,true); + $column = $this->safeColumn($field); + $columnNoQ = $this->safeColumn($field,true); + $targetColumn = $this->safeColumn($targetField); + $targetColumnNoQ = $this->safeColumn($targetField,true); + $keys = $this->getKeys($targetTableNoQ,$tableNoQ); + $needsToAddFK = true; + $needsToDropFK = false; + foreach($keys as $key) { + if ($key['FKTABLE_NAME']==$tableNoQ && $key['FKCOLUMN_NAME']==$columnNoQ) { + //already has an FK + $needsToDropFK = true; + if ((($isDep && $key['DELETE_RULE']==0) || (!$isDep && $key['DELETE_RULE']==3))) { + return false; + } + break; + } + } + + if ($needsToDropFK) { + $sql = "ALTER TABLE $table DROP FOREIGN KEY {$key['FK_NAME']} "; + $this->adapter->exec($sql); + } + $casc = ($isDep ? 'CASCADE' : 'SET NULL'); + $sql = "ALTER TABLE $table ADD CONSTRAINT FOREIGN KEY($column) REFERENCES $targetTable($targetColumn) ON DELETE $casc "; + $this->adapter->exec($sql); + } + + + /** + * Drops all tables in database + */ + public function wipeAll() { + foreach($this->getTables() as $t) { + foreach($this->getKeys($t) as $k) { + $this->adapter->exec("ALTER TABLE \"{$k['FKTABLE_NAME']}\" DROP FOREIGN KEY \"{$k['FK_NAME']}\""); + } + $this->adapter->exec("DROP TABLE \"$t\""); + } + + } + + + /** + * Obtains the keys of a table using the PDO schema function. + * + * @param type $table + * @return type + */ + protected function getKeys($table,$table2=null) { + $pdo = $this->adapter->getDatabase()->getPDO(); + $keys = $pdo->cubrid_schema(PDO::CUBRID_SCH_EXPORTED_KEYS,$table);//print_r($keys); + if ($table2) $keys = array_merge($keys, $pdo->cubrid_schema(PDO::CUBRID_SCH_IMPORTED_KEYS,$table2) );//print_r($keys); + + return $keys; + } + + /** + * Checks table name or column name. + * + * @param string $table table string + * + * @return string $table escaped string + */ + protected function check($table) { + if ($this->quoteCharacter && strpos($table, $this->quoteCharacter)!==false) { + throw new Redbean_Exception_Security('Illegal chars in table name'); + } + return $table; + } + +} + +/** + * RedBean Exception Base + * + * @file RedBean/Exception.php + * @desc Represents the base class for RedBean Exceptions + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_Exception extends LogicException {} + +/** + * RedBean Exception SQL + * + * @file RedBean/Exception/SQL.php + * @desc Represents a generic database exception independent of the underlying driver. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_Exception_SQL extends RuntimeException { + + /** + * Holds the current SQL Strate code. + * @var string + */ + private $sqlState; + + /** + * Returns an ANSI-92 compliant SQL state. + * + * @return string $state ANSI state code + */ + public function getSQLState() { + return $this->sqlState; + } + + /** + * @todo parse state to verify valid ANSI92! + * Stores ANSI-92 compliant SQL state. + * + * @param string $sqlState code + * + * @return void + */ + public function setSQLState( $sqlState ) { + $this->sqlState = $sqlState; + } + + /** + * To String prints both code and SQL state. + * + * @return string $message prints this exception instance as a string + */ + public function __toString() { + return '['.$this->getSQLState().'] - '.$this->getMessage(); + } +} + +/** + * Exception Security. + * Part of the RedBean Exceptions Mechanism. + * + * @file RedBean/Exception + * @desc Represents a subtype in the RedBean Exception System. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * + * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_Exception_Security extends RedBean_Exception {} + +/** + * RedBean Object Oriented DataBase + * + * @file RedBean/OODB.php + * @desc RedBean OODB + * @author Gabor de Mooij and the RedBeanPHP community + * @license BSD/GPLv2 + * + * The RedBean OODB Class is the main class of RedBeanPHP. + * It takes RedBean_OODBBean objects and stores them to and loads them from the + * database as well as providing other CRUD functions. This class acts as a + * object database. + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_OODB extends RedBean_Observable { + + /** + * Chill mode, for fluid mode but with a list of beans / types that + * are considered to be stable and don't need to be modified. + * @var array + */ + protected $chillList = array(); + + /** + * List of dependencies. Format: $type => array($depensOnMe, $andMe) + * @var array + */ + protected $dep = array(); + + /** + * Secret stash. Used for batch loading. + * @var array + */ + protected $stash = NULL; + + /** + * Contains the writer for OODB. + * @var RedBean_Adapter_DBAdapter + */ + protected $writer; + + /** + * Whether this instance of OODB is frozen or not. + * In frozen mode the schema will not de modified, in fluid mode + * the schema can be adjusted to meet the needs of the developer. + * @var boolean + */ + protected $isFrozen = false; + + /** + * Bean Helper. The bean helper to give to the beans. Bean Helpers + * assist beans in getting hold of a toolbox. + * @var null|\RedBean_BeanHelperFacade + */ + protected $beanhelper = null; + + /** + * Association Manager. + * Reference to the Association Manager. The OODB class uses + * the association manager to store many-to-many relations. + * + * @var RedBean_AssociationManager + */ + protected $assocManager = null; + + /** + * The RedBean OODB Class is the main class of RedBean. + * It takes RedBean_OODBBean objects and stores them to and loads them from the + * database as well as providing other CRUD functions. This class acts as a + * object database. + * Constructor, requires a DBAadapter (dependency inversion) + * @param RedBean_Adapter_DBAdapter $adapter + */ + public function __construct( $writer ) { + if ($writer instanceof RedBean_QueryWriter) { + $this->writer = $writer; + } + $this->beanhelper = new RedBean_BeanHelper_Facade(); + } + + /** + * Toggles fluid or frozen mode. In fluid mode the database + * structure is adjusted to accomodate your objects. In frozen mode + * this is not the case. + * + * You can also pass an array containing a selection of frozen types. + * Let's call this chilly mode, it's just like fluid mode except that + * certain types (i.e. tables) aren't touched. + * + * @param boolean|array $trueFalse + */ + public function freeze( $tf ) { + if (is_array($tf)) { + $this->chillList = $tf; + $this->isFrozen = false; + } + else + $this->isFrozen = (boolean) $tf; + } + + + /** + * Returns the current mode of operation of RedBean. + * In fluid mode the database + * structure is adjusted to accomodate your objects. + * In frozen mode + * this is not the case. + * + * @return boolean $yesNo TRUE if frozen, FALSE otherwise + */ + public function isFrozen() { + return (bool) $this->isFrozen; + } + + /** + * Dispenses a new bean (a RedBean_OODBBean Bean Object) + * of the specified type. Always + * use this function to get an empty bean object. Never + * instantiate a RedBean_OODBBean yourself because it needs + * to be configured before you can use it with RedBean. This + * function applies the appropriate initialization / + * configuration for you. + * + * @param string $type type of bean you want to dispense + * + * @return RedBean_OODBBean $bean the new bean instance + */ + public function dispense($type ) { + $bean = new RedBean_OODBBean(); + $bean->setBeanHelper($this->beanhelper); + $bean->setMeta('type',$type ); + $bean->setMeta('sys.id','id'); + $bean->id = 0; + if (!$this->isFrozen) $this->check( $bean ); + $bean->setMeta('tainted',true); + $bean->setMeta('sys.orig',array('id'=>0)); + $this->signal('dispense',$bean ); + return $bean; + } + + /** + * Sets bean helper to be given to beans. + * Bean helpers assist beans in getting a reference to a toolbox. + * + * @param RedBean_IBeanHelper $beanhelper helper + * + * @return void + */ + public function setBeanHelper( RedBean_BeanHelper $beanhelper) { + $this->beanhelper = $beanhelper; + } + + + /** + * Checks whether a RedBean_OODBBean bean is valid. + * If the type is not valid or the ID is not valid it will + * throw an exception: RedBean_Exception_Security. + * @throws RedBean_Exception_Security $exception + * + * @param RedBean_OODBBean $bean the bean that needs to be checked + * + * @return void + */ + public function check( RedBean_OODBBean $bean ) { + //Is all meta information present? + if (!isset($bean->id) ) { + throw new RedBean_Exception_Security('Bean has incomplete Meta Information id '); + } + if (!($bean->getMeta('type'))) { + throw new RedBean_Exception_Security('Bean has incomplete Meta Information II'); + } + //Pattern of allowed characters + $pattern = '/[^a-z0-9_]/i'; + //Does the type contain invalid characters? + if (preg_match($pattern,$bean->getMeta('type'))) { + throw new RedBean_Exception_Security('Bean Type is invalid'); + } + //Are the properties and values valid? + foreach($bean as $prop=>$value) { + if ( + is_array($value) || + (is_object($value)) || + strlen($prop)<1 || + preg_match($pattern,$prop) + ) { + throw new RedBean_Exception_Security("Invalid Bean: property $prop "); + } + } + } + + + /** + * Searches the database for a bean that matches conditions $conditions and sql $addSQL + * and returns an array containing all the beans that have been found. + * + * Conditions need to take form: + * + * array( + * 'PROPERTY' => array( POSSIBLE VALUES... 'John','Steve' ) + * 'PROPERTY' => array( POSSIBLE VALUES... ) + * ); + * + * All conditions are glued together using the AND-operator, while all value lists + * are glued using IN-operators thus acting as OR-conditions. + * + * Note that you can use property names; the columns will be extracted using the + * appropriate bean formatter. + * + * @throws RedBean_Exception_SQL + * + * @param string $type type of beans you are looking for + * @param array $conditions list of conditions + * @param string $addSQL SQL to be used in query + * @param boolean $all whether you prefer to use a WHERE clause or not (TRUE = not) + * + * @return array $beans resulting beans + */ + public function find($type,$conditions=array(),$addSQL=null,$all=false) { + try { + $beans = $this->convertToBeans($type,$this->writer->selectRecord($type,$conditions,$addSQL,false,false,$all)); + return $beans; + }catch(RedBean_Exception_SQL $e) { + if (!$this->writer->sqlStateIn($e->getSQLState(), + array( + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_TABLE, + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN) + )) throw $e; + } + return array(); + } + + + /** + * Checks whether the specified table already exists in the database. + * Not part of the Object Database interface! + * + * @param string $table table name (not type!) + * + * @return boolean $exists whether the given table exists within this database. + */ + public function tableExists($table) { + //does this table exist? + $tables = $this->writer->getTables(); + return in_array(($table), $tables); + } + + + /** + * Processes all column based build commands. + * A build command is an additional instruction for the Query Writer. It is processed only when + * a column gets created. The build command is often used to instruct the writer to write some + * extra SQL to create indexes or constraints. Build commands are stored in meta data of the bean. + * They are only for internal use, try to refrain from using them in your code directly. + * + * @param string $table name of the table to process build commands for + * @param string $property name of the property to process build commands for + * @param RedBean_OODBBean $bean bean that contains the build commands + * + * @return void + */ + protected function processBuildCommands($table, $property, RedBean_OODBBean $bean) { + if ($inx = ($bean->getMeta('buildcommand.indexes'))) { + if (isset($inx[$property])) $this->writer->addIndex($table,$inx[$property],$property); + } + } + + + + /** + * Process groups. Internal function. Processes different kind of groups for + * storage function. Given a list of original beans and a list of current beans, + * this function calculates which beans remain in the list (residue), which + * have been deleted (are in the trashcan) and which beans have been added + * (additions). + * + * @param array $originals originals + * @param array $current the current beans + * @param array $additions beans that have been added + * @param array $trashcan beans that have been deleted + * @param array $residue beans that have been left untouched + * + * @return array $result new relational state + */ + private function processGroups( $originals, $current, $additions, $trashcan, $residue ) { + return array( + array_merge($additions,array_diff($current,$originals)), + array_merge($trashcan,array_diff($originals,$current)), + array_merge($residue,array_intersect($current,$originals)) + ); + } + + + /** + * Figures out the desired type given the cast string ID. + * + * @param string $cast cast identifier + * + * @return integer $typeno + */ + private function getTypeFromCast($cast) { + if ($cast=='string') { + $typeno = $this->writer->scanType('STRING'); + } + elseif ($cast=='id') { + $typeno = $this->writer->getTypeForID(); + } + elseif(isset($this->writer->sqltype_typeno[$cast])) { + $typeno = $this->writer->sqltype_typeno[$cast]; + } + else { + throw new RedBean_Exception('Invalid Cast'); + } + return $typeno; + } + + /** + * Processes an embedded bean. First the bean gets unboxed if possible. + * Then, the bean is stored if needed and finally the ID of the bean + * will be returned. + * + * @param RedBean_OODBBean|Model $v the bean or model + * + * @return integer $id + */ + private function prepareEmbeddedBean($v) { + if (!$v->id || $v->getMeta('tainted')) { + $this->store($v); + } + return $v->id; + } + + /** + * Stores a bean in the database. This function takes a + * RedBean_OODBBean Bean Object $bean and stores it + * in the database. If the database schema is not compatible + * with this bean and RedBean runs in fluid mode the schema + * will be altered to store the bean correctly. + * If the database schema is not compatible with this bean and + * RedBean runs in frozen mode it will throw an exception. + * This function returns the primary key ID of the inserted + * bean. + * + * @throws RedBean_Exception_Security $exception + * + * @param RedBean_OODBBean | RedBean_SimpleModel $bean bean to store + * + * @return integer $newid resulting ID of the new bean + */ + public function store( $bean ) { + if ($bean instanceof RedBean_SimpleModel) $bean = $bean->unbox(); + if (!($bean instanceof RedBean_OODBBean)) throw new RedBean_Exception_Security('OODB Store requires a bean, got: '.gettype($bean)); + $processLists = false; + foreach($bean as $k=>$v) if (is_array($v) || is_object($v)) { $processLists = true; break; } + if (!$processLists && !$bean->getMeta('tainted')) return $bean->getID(); + $this->signal('update', $bean ); + foreach($bean as $k=>$v) if (is_array($v) || is_object($v)) { $processLists = true; break; } + if ($processLists) { + //Define groups + $sharedAdditions = $sharedTrashcan = $sharedresidue = $sharedItems = array(); + $ownAdditions = $ownTrashcan = $ownresidue = array(); + $tmpCollectionStore = array(); + $embeddedBeans = array(); + foreach($bean as $p=>$v) { + if ($v instanceof RedBean_SimpleModel) $v = $v->unbox(); + if ($v instanceof RedBean_OODBBean) { + $linkField = $p.'_id'; + $bean->$linkField = $this->prepareEmbeddedBean($v); + $bean->setMeta('cast.'.$linkField,'id'); + $embeddedBeans[$linkField] = $v; + $tmpCollectionStore[$p]=$bean->$p; + $bean->removeProperty($p); + } + if (is_array($v)) { + $originals = $bean->getMeta('sys.shadow.'.$p); + if (!$originals) $originals = array(); + if (strpos($p,'own')===0) { + list($ownAdditions,$ownTrashcan,$ownresidue)=$this->processGroups($originals,$v,$ownAdditions,$ownTrashcan,$ownresidue); + $bean->removeProperty($p); + } + elseif (strpos($p,'shared')===0) { + list($sharedAdditions,$sharedTrashcan,$sharedresidue)=$this->processGroups($originals,$v,$sharedAdditions,$sharedTrashcan,$sharedresidue); + $bean->removeProperty($p); + } + else {} + } + } + } + $this->storeBean($bean); + + if ($processLists) { + $this->processEmbeddedBeans($bean,$embeddedBeans); + $myFieldLink = $bean->getMeta('type').'_id'; + //Handle related beans + $this->processTrashcan($bean,$ownTrashcan); + $this->processAdditions($bean,$ownAdditions); + $this->processResidue($ownresidue); + foreach($sharedTrashcan as $trash) $this->assocManager->unassociate($trash,$bean); + $this->processSharedAdditions($bean,$sharedAdditions); + foreach($sharedresidue as $residue) $this->store($residue); + } + $this->signal('after_update',$bean); + return (int) $bean->id; + } + + /** + * Stores a cleaned bean; i.e. only scalar values. This is the core of the store() + * method. When all lists and embedded beans (parent objects) have been processed and + * removed from the original bean the bean is passed to this method to be stored + * in the database. + * + * @param RedBean_OODBBean $bean the clean bean + */ + private function storeBean(RedBean_OODBBean $bean) { + if (!$this->isFrozen) $this->check($bean); + //what table does it want + $table = $bean->getMeta('type'); + if ($bean->getMeta('tainted')) { + //Does table exist? If not, create + if (!$this->isFrozen && !$this->tableExists($this->writer->safeTable($table,true))) { + $this->writer->createTable( $table ); + $bean->setMeta('buildreport.flags.created',true); + } + if (!$this->isFrozen) { + $columns = $this->writer->getColumns($table) ; + } + //does the table fit? + $insertvalues = array(); + $insertcolumns = array(); + $updatevalues = array(); + foreach( $bean as $p=>$v ) { + $origV = $v; + if ($p!='id') { + if (!$this->isFrozen) { + //Not in the chill list? + if (!in_array($bean->getMeta('type'),$this->chillList)) { + //Does the user want to specify the type? + if ($bean->getMeta("cast.$p",-1)!==-1) { + $cast = $bean->getMeta("cast.$p"); + $typeno = $this->getTypeFromCast($cast); + } + else { + $cast = false; + //What kind of property are we dealing with? + $typeno = $this->writer->scanType($v,true); + } + //Is this property represented in the table? + if (isset($columns[$this->writer->safeColumn($p,true)])) { + //rescan + $v = $origV; + if (!$cast) $typeno = $this->writer->scanType($v,false); + //yes it is, does it still fit? + $sqlt = $this->writer->code($columns[$this->writer->safeColumn($p,true)]); + if ($typeno > $sqlt) { + //no, we have to widen the database column type + $this->writer->widenColumn( $table, $p, $typeno ); + $bean->setMeta('buildreport.flags.widen',true); + } + } + else { + //no it is not + $this->writer->addColumn($table, $p, $typeno); + $bean->setMeta('buildreport.flags.addcolumn',true); + //@todo: move build commands here... more practical + $this->processBuildCommands($table,$p,$bean); + } + } + } + //Okay, now we are sure that the property value will fit + $insertvalues[] = $v; + $insertcolumns[] = $p; + $updatevalues[] = array( 'property'=>$p, 'value'=>$v ); + } + } + if (!$this->isFrozen && ($uniques = $bean->getMeta('buildcommand.unique'))) { + foreach($uniques as $unique) $this->writer->addUniqueIndex( $table, $unique ); + } + $rs = $this->writer->updateRecord( $table, $updatevalues, $bean->id ); + $bean->id = $rs; + $bean->setMeta('tainted',false); + } + } + + /** + * Processes a list of beans from a bean. A bean may contain lists. This + * method handles shared addition lists; i.e. the $bean->sharedObject properties. + * + * @param RedBean_OODBBean $bean the bean + * @param array $sharedAdditions list with shared additions + */ + private function processSharedAdditions($bean,$sharedAdditions) { + foreach($sharedAdditions as $addition) { + if ($addition instanceof RedBean_OODBBean) { + $this->assocManager->associate($addition,$bean); + } + else { + throw new RedBean_Exception_Security('Array may only contain RedBean_OODBBeans'); + } + } + } + + /** + * Processes a list of beans from a bean. A bean may contain lists. This + * method handles own lists; i.e. the $bean->ownObject properties. + * A residue is a bean in an own-list that stays where it is. This method + * checks if there have been any modification to this bean, in that case + * the bean is stored once again, otherwise the bean will be left untouched. + * + * @param RedBean_OODBBean $bean the bean + * @param array $ownresidue list + */ + private function processResidue($ownresidue) { + foreach($ownresidue as $residue) { + if ($residue->getMeta('tainted')) { + $this->store($residue); + } + } + } + + /** + * Processes a list of beans from a bean. A bean may contain lists. This + * method handles own lists; i.e. the $bean->ownObject properties. + * A trash can bean is a bean in an own-list that has been removed + * (when checked with the shadow). This method + * checks if the bean is also in the dependency list. If it is the bean will be removed. + * If not, the connection between the bean and the owner bean will be broken by + * setting the ID to NULL. + * + * @param RedBean_OODBBean $bean the bean + * @param array $ownTrashcan list + */ + private function processTrashcan($bean,$ownTrashcan) { + $myFieldLink = $bean->getMeta('type').'_id'; + if (is_array($ownTrashcan) && count($ownTrashcan)>0) { + $first = reset($ownTrashcan); + if ($first instanceof RedBean_OODBBean) { + $alias = $bean->getMeta('sys.alias.'.$first->getMeta('type')); + if ($alias) $myFieldLink = $alias.'_id'; + } + } + foreach($ownTrashcan as $trash) { + if (isset($this->dep[$trash->getMeta('type')]) && in_array($bean->getMeta('type'),$this->dep[$trash->getMeta('type')])) { + $this->trash($trash); + } + else { + $trash->$myFieldLink = null; + $this->store($trash); + } + } + } + + /** + * Processes embedded beans. + * Each embedded bean will be indexed and foreign keys will + * be created if the bean is in the dependency list. + * + * @param RedBean_OODBBean $bean bean + * @param array $embeddedBeans embedded beans + */ + private function processEmbeddedBeans($bean, $embeddedBeans) { + foreach($embeddedBeans as $linkField=>$embeddedBean) { + if (!$this->isFrozen) { + $this->writer->addIndex($bean->getMeta('type'), + 'index_foreignkey_'.$bean->getMeta('type').'_'.$embeddedBean->getMeta('type'), + $linkField); + $isDep = $this->isDependentOn($bean->getMeta('type'),$embeddedBean->getMeta('type')); + $this->writer->addFK($bean->getMeta('type'),$embeddedBean->getMeta('type'),$linkField,'id',$isDep); + } + } + + } + + /** + * Part of the store() functionality. + * Handles all new additions after the bean has been saved. + * Stores addition bean in own-list, extracts the id and + * adds a foreign key. Also adds a constraint in case the type is + * in the dependent list. + * + * @param RedBean_OODBBean $bean bean + * @param array $ownAdditions list of addition beans in own-list + */ + private function processAdditions($bean,$ownAdditions) { + $myFieldLink = $bean->getMeta('type').'_id'; + if ($bean && count($ownAdditions)>0) { + $first = reset($ownAdditions); + if ($first instanceof RedBean_OODBBean) { + $alias = $bean->getMeta('sys.alias.'.$first->getMeta('type')); + if ($alias) $myFieldLink = $alias.'_id'; + } + } + foreach($ownAdditions as $addition) { + if ($addition instanceof RedBean_OODBBean) { + $addition->$myFieldLink = $bean->id; + $addition->setMeta('cast.'.$myFieldLink,'id'); + $this->store($addition); + + if (!$this->isFrozen) { + $this->writer->addIndex($addition->getMeta('type'), + 'index_foreignkey_'.$addition->getMeta('type').'_'.$bean->getMeta('type'), + $myFieldLink); + $isDep = $this->isDependentOn($addition->getMeta('type'),$bean->getMeta('type')); + $this->writer->addFK($addition->getMeta('type'),$bean->getMeta('type'),$myFieldLink,'id',$isDep); + } + } + else { + throw new RedBean_Exception_Security('Array may only contain RedBean_OODBBeans'); + } + } + + } + + /** + * Checks whether reference type has been marked as dependent on target type. + * This is the result of setting reference type as a key in R::dependencies() and + * putting target type in its array. + * + * @param string $refType reference type + * @param string $otherType other type / target type + * + * @return boolean + */ + protected function isDependentOn($refType,$otherType) { + return (boolean) (isset($this->dep[$refType]) && in_array($otherType,$this->dep[$refType])); + } + + + /** + * Loads a bean from the object database. + * It searches for a RedBean_OODBBean Bean Object in the + * database. It does not matter how this bean has been stored. + * RedBean uses the primary key ID $id and the string $type + * to find the bean. The $type specifies what kind of bean you + * are looking for; this is the same type as used with the + * dispense() function. If RedBean finds the bean it will return + * the RedBean_OODB Bean object; if it cannot find the bean + * RedBean will return a new bean of type $type and with + * primary key ID 0. In the latter case it acts basically the + * same as dispense(). + * + * Important note: + * If the bean cannot be found in the database a new bean of + * the specified type will be generated and returned. + * + * @param string $type type of bean you want to load + * @param integer $id ID of the bean you want to load + * + * @return RedBean_OODBBean $bean loaded bean + */ + public function load($type,$id) { + $bean = $this->dispense( $type ); + if ($this->stash && isset($this->stash[$id])) { + $row = $this->stash[$id]; + } + else { + try { + $rows = $this->writer->selectRecord($type,array('id'=>array($id))); + }catch(RedBean_Exception_SQL $e ) { + if ( + $this->writer->sqlStateIn($e->getSQLState(), + array( + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_TABLE) + ) + ) { + $rows = 0; + if ($this->isFrozen) throw $e; //only throw if frozen; + } + } + if (empty($rows)) return $bean; // $this->dispense($type); -- no need... + $row = array_pop($rows); + } + $bean->setMeta('sys.orig',$row); + foreach($row as $p=>$v) { + //populate the bean with the database row + $bean->$p = $v; + } + $this->signal('open',$bean ); + $bean->setMeta('tainted',false); + return $bean; + } + + /** + * Removes a bean from the database. + * This function will remove the specified RedBean_OODBBean + * Bean Object from the database. + * + * @throws RedBean_Exception_Security $exception + * + * @param RedBean_OODBBean|RedBean_SimpleModel $bean bean you want to remove from database + */ + public function trash( $bean ) { + if ($bean instanceof RedBean_SimpleModel) $bean = $bean->unbox(); + if (!($bean instanceof RedBean_OODBBean)) throw new RedBean_Exception_Security('OODB Store requires a bean, got: '.gettype($bean)); + $this->signal('delete',$bean); + foreach($bean as $p=>$v) { + if ($v instanceof RedBean_OODBBean) { + $bean->removeProperty($p); + } + if (is_array($v)) { + if (strpos($p,'own')===0) { + $bean->removeProperty($p); + } + elseif (strpos($p,'shared')===0) { + $bean->removeProperty($p); + } + } + } + if (!$this->isFrozen) $this->check( $bean ); + try { + $this->writer->selectRecord($bean->getMeta('type'), + array('id' => array( $bean->id) ),null,true ); + }catch(RedBean_Exception_SQL $e) { + if (!$this->writer->sqlStateIn($e->getSQLState(), + array( + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_TABLE) + )) throw $e; + } + $bean->id = 0; + $this->signal('after_delete', $bean ); + } + + /** + * Returns an array of beans. Pass a type and a series of ids and + * this method will bring you the correspondig beans. + * + * important note: Because this method loads beans using the load() + * function (but faster) it will return empty beans with ID 0 for + * every bean that could not be located. The resulting beans will have the + * passed IDs as their keys. + * + * @param string $type type of beans + * @param array $ids ids to load + * + * @return array $beans resulting beans (may include empty ones) + */ + public function batch( $type, $ids ) { + if (!$ids) return array(); + $collection = array(); + try { + $rows = $this->writer->selectRecord($type,array('id'=>$ids)); + }catch(RedBean_Exception_SQL $e) { + if (!$this->writer->sqlStateIn($e->getSQLState(), + array( + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_TABLE) + )) throw $e; + + $rows = false; + } + $this->stash = array(); + if (!$rows) return array(); + foreach($rows as $row) { + $this->stash[$row['id']] = $row; + } + foreach($ids as $id) { + $collection[ $id ] = $this->load( $type, $id ); + } + $this->stash = NULL; + return $collection; + } + + /** + * This is a convenience method; it converts database rows + * (arrays) into beans. Given a type and a set of rows this method + * will return an array of beans of the specified type loaded with + * the data fields provided by the result set from the database. + * + * @param string $type type of beans you would like to have + * @param array $rows rows from the database result + * + * @return array $collectionOfBeans collection of beans + */ + public function convertToBeans($type, $rows) { + $collection = array(); + $this->stash = array(); + foreach($rows as $row) { + $id = $row['id']; + $this->stash[$id] = $row; + $collection[ $id ] = $this->load( $type, $id ); + } + $this->stash = NULL; + return $collection; + } + + /** + * Returns the number of beans we have in DB of a given type. + * + * @param string $type type of bean we are looking for + * @param string $addSQL additional SQL snippet + * @param array $params parameters to bind to SQL + * + * @return integer $num number of beans found + */ + public function count($type,$addSQL='',$params=array()) { + try { + return (int) $this->writer->count($type,$addSQL,$params); + }catch(RedBean_Exception_SQL $e) { + if (!$this->writer->sqlStateIn($e->getSQLState(), + array(RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_TABLE) + )) throw $e; + } + return 0; + } + + /** + * Trash all beans of a given type. + * + * @param string $type type + * + * @return boolean $yesNo whether we actually did some work or not.. + */ + public function wipe($type) { + try { + $this->writer->wipe($type); + return true; + }catch(RedBean_Exception_SQL $e) { + if (!$this->writer->sqlStateIn($e->getSQLState(), + array(RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_TABLE) + )) throw $e; + return false; + } + } + + /** + * Returns an Association Manager for use with OODB. + * A simple getter function to obtain a reference to the association manager used for + * storage and more. + * + * @throws Exception + * @return RedBean_AssociationManager $assoc Association Manager + */ + public function getAssociationManager() { + if (!isset($this->assocManager)) throw new Exception('No association manager available.'); + return $this->assocManager; + } + + /** + * Sets the association manager instance to be used by this OODB. + * A simple setter function to set the association manager to be used for storage and + * more. + * + * @param RedBean_AssociationManager $assoc sets the association manager to be used + * + * @return void + */ + public function setAssociationManager(RedBean_AssociationManager $assoc) { + $this->assocManager = $assoc; + } + + + /** + * Sets a dependency list. Dependencies can be used to make + * certain beans depend on others. This causes dependent beans to get removed + * once the bean they depend on has been removed as well. + * A dependency takes the form: + * + * $me => depends on array( $bean1, $bean2 ) + * + * For instance a to inform RedBeanPHP about the fact that a page + * depends on a book: + * + * 'page' => array('book') + * + * A bean can depend on multiple other beans. + * + * A dependency does two things: + * + * 1. Adds a ON CASCADE DELETE + * 2. trashes the depending bean if the entry in the ownList is removed + * + * @param array $dep + */ + public function setDepList($dep) { + $this->dep = $dep; + } + + /** + * Preloads certain properties for beans. + * Understands aliases. + * + * Usage: $redbean->preload($books,array('coauthor'=>'author')); + * + * @param array $beans beans + * @param array $types types to load + */ + public function preload($beans, $types) { + foreach($types as $key => $type) { + $map = array(); + $field = (is_numeric($key)) ? $type : $key; + $ids = array(); + foreach($beans as $bean) { + if($id = $bean->{$field.'_id'}){ + $ids[$id] = $id; + if (!isset($map[$id])) $map[$id] = array(); + $map[$id][] = $bean; + } + } + $parents = $this->batch($type,$ids); + foreach($parents as $parent) { + foreach($map[$parent->id] as $childBean) { + $childBean->setProperty($field,$parent); + } + } + } + } + +} + + + + +/** + * ToolBox + * Contains most important redbean tools + * + * @file RedBean/ToolBox.php + * @desc A RedBeanPHP-wide service locator + * @author Gabor de Mooij and the RedBeanPHP community + * @license BSD/GPLv2 + * + * The ToolBox acts as a resource locator for RedBean but can + * be integrated in larger resource locators (nested). + * It does not do anything more than just store the three most + * important RedBean resources (tools): the database adapter, + * the RedBeanPHP core class (oodb) and the query writer. + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_ToolBox { + + /** + * Reference to the RedBeanPHP OODB Object Database instance + * @var RedBean_OODB + */ + protected $oodb; + + /** + * Reference to the Query Writer + * @var RedBean_QueryWriter + */ + protected $writer; + + /** + * Reference to the database adapter + * @var RedBean_Adapter_DBAdapter + */ + protected $adapter; + + /** + * Constructor. + * The Constructor of the ToolBox takes three arguments: a RedBean_OODB $redbean + * object database, a RedBean_Adapter $databaseAdapter and a + * RedBean_QueryWriter $writer. It stores these objects inside and acts as + * a micro service locator. You can pass the toolbox to any object that needs + * one of the RedBean core objects to interact with. + * + * @param RedBean_OODB $oodb Object Database + * @param RedBean_Adapter_DBAdapter $adapter Adapter + * @param RedBean_QueryWriter $writer Writer + * + * return RedBean_ToolBox $toolbox Toolbox + */ + public function __construct(RedBean_OODB $oodb,RedBean_Adapter $adapter,RedBean_QueryWriter $writer) { + $this->oodb = $oodb; + $this->adapter = $adapter; + $this->writer = $writer; + return $this; + } + + /** + * The Toolbox acts as a kind of micro service locator, providing just the + * most important objects that make up RedBean. You can pass the toolkit to + * any object that needs one of these objects to function properly. + * Returns the QueryWriter; normally you do not use this object but other + * object might want to use the default RedBean query writer to be + * database independent. + * + * @return RedBean_QueryWriter $writer writer + */ + public function getWriter() { + return $this->writer; + } + + /** + * The Toolbox acts as a kind of micro service locator, providing just the + * most important objects that make up RedBean. You can pass the toolkit to + * any object that needs one of these objects to function properly. + * Retruns the RedBean OODB Core object. The RedBean OODB object is + * the ultimate core of Redbean. It provides the means to store and load + * beans. Extract this object immediately after invoking a kickstart method. + * + * @return RedBean_OODB $oodb Object Database + */ + public function getRedBean() { + return $this->oodb; + } + + /** + * The Toolbox acts as a kind of micro service locator, providing just the + * most important objects that make up RedBean. You can pass the toolkit to + * any object that needs one of these objects to function properly. + * Returns the adapter. The Adapter can be used to perform queries + * on the database directly. + * + * @return RedBean_Adapter_DBAdapter $adapter Adapter + */ + public function getDatabaseAdapter() { + return $this->adapter; + } +} + +/** + * RedBean Association + * + * @file RedBean/AssociationManager.php + * @desc Manages simple bean associations. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_AssociationManager extends RedBean_Observable { + /** + * Contains a reference to the Object Database OODB + * @var RedBean_OODB + */ + protected $oodb; + + /** + * Contains a reference to the Database Adapter + * @var RedBean_Adapter_DBAdapter + */ + protected $adapter; + + /** + * Contains a reference to the Query Writer + * @var RedBean_QueryWriter + */ + protected $writer; + + + /** + * Constructor + * + * @param RedBean_ToolBox $tools toolbox + */ + public function __construct( RedBean_ToolBox $tools ) { + $this->oodb = $tools->getRedBean(); + $this->adapter = $tools->getDatabaseAdapter(); + $this->writer = $tools->getWriter(); + $this->toolbox = $tools; + } + + /** + * Creates a table name based on a types array. + * Manages the get the correct name for the linking table for the + * types provided. + * + * @todo find a nice way to decouple this class from QueryWriter? + * + * @param array $types 2 types as strings + * + * @return string $table table + */ + public function getTable( $types ) { + return RedBean_QueryWriter_AQueryWriter::getAssocTableFormat($types); + } + /** + * Associates two beans with eachother using a many-to-many relation. + * + * @param RedBean_OODBBean $bean1 bean1 + * @param RedBean_OODBBean $bean2 bean2 + */ + public function associate($beans1, $beans2) { + $results = array(); + if (!is_array($beans1)) $beans1 = array($beans1); + if (!is_array($beans2)) $beans2 = array($beans2); + foreach($beans1 as $bean1) { + foreach($beans2 as $bean2) { + $table = $this->getTable( array($bean1->getMeta('type') , $bean2->getMeta('type')) ); + $bean = $this->oodb->dispense($table); + $results[] = $this->associateBeans( $bean1, $bean2, $bean ); + } + } + return (count($results)>1) ? $results : reset($results); + } + + + /** + * Associates a pair of beans. This method associates two beans, no matter + * what types.Accepts a base bean that contains data for the linking record. + * + * @param RedBean_OODBBean $bean1 first bean + * @param RedBean_OODBBean $bean2 second bean + * @param RedBean_OODBBean $bean base bean + * + * @return mixed $id either the link ID or null + */ + protected function associateBeans(RedBean_OODBBean $bean1, RedBean_OODBBean $bean2, RedBean_OODBBean $bean) { + + $property1 = $bean1->getMeta('type') . '_id'; + $property2 = $bean2->getMeta('type') . '_id'; + if ($property1==$property2) $property2 = $bean2->getMeta('type').'2_id'; + //add a build command for Unique Indexes + $bean->setMeta('buildcommand.unique' , array(array($property1, $property2))); + //add a build command for Single Column Index (to improve performance in case unqiue cant be used) + $indexName1 = 'index_for_'.$bean->getMeta('type').'_'.$property1; + $indexName2 = 'index_for_'.$bean->getMeta('type').'_'.$property2; + $bean->setMeta('buildcommand.indexes', array($property1=>$indexName1,$property2=>$indexName2)); + $this->oodb->store($bean1); + $this->oodb->store($bean2); + $bean->setMeta("cast.$property1","id"); + $bean->setMeta("cast.$property2","id"); + $bean->$property1 = $bean1->id; + $bean->$property2 = $bean2->id; + try { + $id = $this->oodb->store( $bean ); + //On creation, add constraints.... + if (!$this->oodb->isFrozen() && + $bean->getMeta('buildreport.flags.created')){ + $bean->setMeta('buildreport.flags.created',0); + if (!$this->oodb->isFrozen()) + $this->writer->addConstraint( $bean1, $bean2 ); + } + $results[] = $id; + } + catch(RedBean_Exception_SQL $e) { + if (!$this->writer->sqlStateIn($e->getSQLState(), + array( + RedBean_QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION + ))) throw $e; + } + + } + + /** + * Returns all ids of beans of type $type that are related to $bean. If the + * $getLinks parameter is set to boolean TRUE this method will return the ids + * of the association beans instead. You can also add additional SQL. This SQL + * will be appended to the original query string used by this method. Note that this + * method will not return beans, just keys. For a more convenient method see the R-facade + * method related(), that is in fact a wrapper for this method that offers a more + * convenient solution. If you want to make use of this method, consider the + * OODB batch() method to convert the ids to beans. + * + * Since 3.2, you can now also pass an array of beans instead just one + * bean as the first parameter. + * + * @throws RedBean_Exception_SQL + * + * @param RedBean_OODBBean|array $bean reference bean + * @param string $type target type + * @param bool $getLinks whether you are interested in the assoc records + * @param bool $sql room for additional SQL + * + * @return array $ids + */ + public function related( $bean, $type, $getLinks=false, $sql=false) { + if (!is_array($bean) && !($bean instanceof RedBean_OODBBean)) throw new RedBean_Exception_Security('Expected array or RedBean_OODBBean but got:'.gettype($bean)); + $ids = array(); + if (is_array($bean)) { + $beans = $bean; + foreach($beans as $b) { + if (!($b instanceof RedBean_OODBBean)) throw new RedBean_Exception_Security('Expected RedBean_OODBBean in array but got:'.gettype($b)); + $ids[] = $b->id; + } + $bean = reset($beans); + } + else $ids[] = $bean->id; + $table = $this->getTable( array($bean->getMeta('type') , $type) ); + if ($type==$bean->getMeta('type')) { + $type .= '2'; + $cross = 1; + } + else $cross=0; + if (!$getLinks) $targetproperty = $type.'_id'; else $targetproperty='id'; + + $property = $bean->getMeta('type').'_id'; + try { + $sqlFetchKeys = $this->writer->selectRecord( + $table, + array( $property => $ids ), + $sql, + false + ); + $sqlResult = array(); + foreach( $sqlFetchKeys as $row ) { + if (isset($row[$targetproperty])) { + $sqlResult[] = $row[$targetproperty]; + } + } + if ($cross) { + $sqlFetchKeys2 = $this->writer->selectRecord( + $table, + array( $targetproperty => $ids), + $sql, + false + ); + foreach( $sqlFetchKeys2 as $row ) { + if (isset($row[$property])) { + $sqlResult[] = $row[$property]; + } + } + } + return $sqlResult; //or returns rows in case of $sql != empty + }catch(RedBean_Exception_SQL $e) { + if (!$this->writer->sqlStateIn($e->getSQLState(), + array( + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_TABLE) + )) throw $e; + return array(); + } + } + + /** + * Breaks the association between two beans. This method unassociates two beans. If the + * method succeeds the beans will no longer form an association. In the database + * this means that the association record will be removed. This method uses the + * OODB trash() method to remove the association links, thus giving FUSE models the + * opportunity to hook-in additional business logic. If the $fast parameter is + * set to boolean TRUE this method will remove the beans without their consent, + * bypassing FUSE. This can be used to improve performance. + * + * @param RedBean_OODBBean $bean1 first bean + * @param RedBean_OODBBean $bean2 second bean + * @param boolean $fast If TRUE, removes the entries by query without FUSE + */ + public function unassociate($beans1, $beans2, $fast=null) { + if (!is_array($beans1)) $beans1 = array($beans1); + if (!is_array($beans2)) $beans2 = array($beans2); + foreach($beans1 as $bean1) { + foreach($beans2 as $bean2) { + + $this->oodb->store($bean1); + $this->oodb->store($bean2); + $table = $this->getTable( array($bean1->getMeta('type') , $bean2->getMeta('type')) ); + $type = $bean1->getMeta('type'); + if ($type==$bean2->getMeta('type')) { + $type .= '2'; + $cross = 1; + } + else $cross = 0; + $property1 = $type.'_id'; + $property2 = $bean2->getMeta('type').'_id'; + $value1 = (int) $bean1->id; + $value2 = (int) $bean2->id; + try { + $rows = $this->writer->selectRecord($table,array( + $property1 => array($value1), $property2=>array($value2)),null,$fast + ); + if ($cross) { + $rows2 = $this->writer->selectRecord($table,array( + $property2 => array($value1), $property1=>array($value2)),null,$fast + ); + if ($fast) continue; + $rows = array_merge($rows,$rows2); + } + if ($fast) continue; + $beans = $this->oodb->convertToBeans($table,$rows); + foreach($beans as $link) { + $this->oodb->trash($link); + } + }catch(RedBean_Exception_SQL $e) { + if (!$this->writer->sqlStateIn($e->getSQLState(), + array( + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_TABLE) + )) throw $e; + } + } + } + } + + /** + * Removes all relations for a bean. This method breaks every connection between + * a certain bean $bean and every other bean of type $type. Warning: this method + * is really fast because it uses a direct SQL query however it does not inform the + * models about this. If you want to notify FUSE models about deletion use a foreach-loop + * with unassociate() instead. (that might be slower though) + * + * @param RedBean_OODBBean $bean reference bean + * @param string $type type of beans that need to be unassociated + * + * @return void + */ + public function clearRelations(RedBean_OODBBean $bean, $type) { + $this->oodb->store($bean); + $table = $this->getTable( array($bean->getMeta('type') , $type) ); + if ($type==$bean->getMeta('type')) { + $property2 = $type.'2_id'; + $cross = 1; + } + else $cross = 0; + $property = $bean->getMeta('type').'_id'; + try { + $this->writer->selectRecord( $table, array($property=>array($bean->id)),null,true); + if ($cross) { + $this->writer->selectRecord( $table, array($property2=>array($bean->id)),null,true); + } + }catch(RedBean_Exception_SQL $e) { + if (!$this->writer->sqlStateIn($e->getSQLState(), + array( + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_TABLE) + )) throw $e; + } + } + + /** + * Given two beans this function returns TRUE if they are associated using a + * many-to-many association, FALSE otherwise. + * + * @throws RedBean_Exception_SQL + * + * @param RedBean_OODBBean $bean1 bean + * @param RedBean_OODBBean $bean2 bean + * + * @return bool $related whether they are associated N-M + */ + public function areRelated(RedBean_OODBBean $bean1, RedBean_OODBBean $bean2) { + if (!$bean1->getID() || !$bean2->getID()) return false; + $table = $this->getTable( array($bean1->getMeta('type') , $bean2->getMeta('type')) ); + $type = $bean1->getMeta('type'); + if ($type==$bean2->getMeta('type')) { + $type .= '2'; + $cross = 1; + } + else $cross = 0; + $property1 = $type.'_id'; + $property2 = $bean2->getMeta('type').'_id'; + $value1 = (int) $bean1->id; + $value2 = (int) $bean2->id; + try { + $rows = $this->writer->selectRecord($table,array( + $property1 => array($value1), $property2=>array($value2)),null + ); + if ($cross) { + $rows2 = $this->writer->selectRecord($table,array( + $property2 => array($value1), $property1=>array($value2)),null + ); + $rows = array_merge($rows,$rows2); + } + }catch(RedBean_Exception_SQL $e) { + if (!$this->writer->sqlStateIn($e->getSQLState(), + array( + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_TABLE) + )) throw $e; + return false; + } + return (count($rows)>0); + } +} + +/** + * RedBean Extended Association + * + * @file RedBean/ExtAssociationManager.php + * @desc Manages complex bean associations. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_AssociationManager_ExtAssociationManager extends RedBean_AssociationManager { + + /** + * Associates two beans with eachother. This method connects two beans with eachother, just + * like the other associate() method in the Association Manager. The difference is however + * that this method accepts a base bean, this bean will be used as the basis of the + * association record in the link table. You can thus add additional properties and + * even foreign keys. + * + * @param RedBean_OODBBean $bean1 bean 1 + * @param RedBean_OODBBean $bean2 bean 2 + * @param RedBean_OODBBean $bbean base bean for association record + * + * @return void + */ + public function extAssociate(RedBean_OODBBean $bean1, RedBean_OODBBean $bean2, RedBean_OODBBean $baseBean ) { + $table = $this->getTable( array($bean1->getMeta('type') , $bean2->getMeta('type')) ); + $baseBean->setMeta('type', $table ); + return $this->associateBeans( $bean1, $bean2, $baseBean ); + } +} + +/** + * RedBean Setup + * Helper class to quickly setup RedBean for you. + * + * @file RedBean/Setup.php + * @desc Helper class to quickly setup RedBean for you + * @author Gabor de Mooij and the RedBeanPHP community + * @license BSD/GPLv2 + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_Setup { + + /** + * This method checks the DSN string. If the DSN string contains a + * database name that is not supported by RedBean yet then it will + * throw an exception RedBean_Exception_NotImplemented. In any other + * case this method will just return boolean TRUE. + * @throws RedBean_Exception_NotImplemented + * @param string $dsn + * @return boolean $true + */ + private static function checkDSN($dsn) { + $dsn = trim($dsn); + $dsn = strtolower($dsn); + if ( + strpos($dsn, 'mysql:')!==0 + && strpos($dsn,'sqlite:')!==0 + && strpos($dsn,'pgsql:')!==0 + && strpos($dsn,'cubrid:')!==0 + && strpos($dsn,'oracle:')!==0 + ) { + trigger_error('Unsupported DSN'); + } + else { + return true; + } + } + + + /** + * Generic Kickstart method. + * This is the generic kickstarter. It will prepare a database connection + * using the $dsn, the $username and the $password you provide. + * If $frozen is boolean TRUE it will start RedBean in frozen mode, meaning + * that the database cannot be altered. If RedBean is started in fluid mode + * it will adjust the schema of the database if it detects an + * incompatible bean. + * This method returns a RedBean_Toolbox $toolbox filled with a + * RedBean_Adapter, a RedBean_QueryWriter and most importantly a + * RedBean_OODB; the object database. To start storing beans in the database + * simply say: $redbean = $toolbox->getRedBean(); Now you have a reference + * to the RedBean object. + * Optionally instead of using $dsn you may use an existing PDO connection. + * Example: RedBean_Setup::kickstart($existingConnection, true); + * + * @param string|PDO $dsn Database Connection String (or PDO instance) + * @param string $username Username for database + * @param string $password Password for database + * @param boolean $frozen Start in frozen mode? + * + * @return RedBean_ToolBox $toolbox + */ + public static function kickstart($dsn,$username=NULL,$password=NULL,$frozen=false ) { + if ($dsn instanceof PDO) { + $db = new RedBean_Driver_PDO($dsn); + $dsn = $db->getDatabaseType(); + } + else { + self::checkDSN($dsn); + if (strpos($dsn, 'oracle') === 0) + $db = new RedBean_Driver_OCI($dsn,$username,$password); + else + $db = new RedBean_Driver_PDO($dsn,$username,$password); + + } + $adapter = new RedBean_Adapter_DBAdapter($db); + if (strpos($dsn,'pgsql')===0) { + $writer = new RedBean_QueryWriter_PostgreSQL($adapter); + } + else if (strpos($dsn,'sqlite')===0) { + $writer = new RedBean_QueryWriter_SQLiteT($adapter); + } + else if (strpos($dsn,'cubrid')===0) { + $writer = new RedBean_QueryWriter_CUBRID($adapter); + } + else if (strpos($dsn,'oracle')===0) { $writer = new RedBean_QueryWriter_Oracle($adapter); } //layout important for unit test - this line cannot always be tested. + else { + $writer = new RedBean_QueryWriter_MySQL($adapter); + } + $redbean = new RedBean_OODB($writer); + if ($frozen) $redbean->freeze(true); + $toolbox = new RedBean_ToolBox($redbean,$adapter,$writer); + //deliver everything back in a neat toolbox + return $toolbox; + } +} + +/** + * RedBean interface for Model Formatting - Part of FUSE + * + * @file RedBean/ModelFormatter.php + * @desc RedBean IModelFormatter + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +interface RedBean_IModelFormatter { + + /** + * ModelHelper will call this method of the class + * you provide to discover the model + * + * @param string $model + * + * @return string $formattedModel + */ + public function formatModel( $model ); + + +} + + +/** + * RedBean Logging + * + * @file RedBean/Logging.php + * @desc Logging interface for RedBeanPHP ORM + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * Provides a uniform and convenient logging + * interface throughout RedBeanPHP. + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + * + */ +interface RedBean_Logger { + + /** + * Method used to log messages. + * Writes the specified message to the log document whatever + * that may be (files, database etc). Provides a uniform + * interface for logging throughout RedBeanPHP. + * + * @param string $message the message to log. (optional) + */ + public function log(); + +} + + +/** + * RedBean class for Logging + * + * @file RedBean/Logger.php + * @desc Logger + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * copyright (c) G.J.G.T. (Gabor) de Mooij + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_Logger_Default implements RedBean_Logger { + + /** + * Default logger method logging to STDOUT. + * This is the default/reference implementation of a logger. + * This method will write the message value to STDOUT (screen). + * + * @param $message (optional) + */ + public function log() { + if (func_num_args() > 0) { + foreach (func_get_args() as $argument) { + if (is_array($argument)) echo print_r($argument,true); else echo $argument; + echo "
\n"; + } + } + } +} + + + + +/** + * RedBean Bean Helper Interface + * + * @file RedBean/IBeanHelper.php + * @desc Interface for Bean Helper. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * Interface for Bean Helper. + * A little bolt that glues the whole machinery together. + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + * + */ +interface RedBean_BeanHelper { + + /** + * @abstract + * @return RedBean_Toolbox $toolbox toolbox + */ + public function getToolbox(); + + public function getModelForBean(RedBean_OODBBean $bean); + +} + + +/** + * RedBean Bean Helper + * + * @file RedBean/BeanHelperFacade.php + * @desc Finds the toolbox for the bean. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_BeanHelper_Facade implements RedBean_BeanHelper { + + /** + * Returns a reference to the toolbox. This method returns a toolbox + * for beans that need to use toolbox functions. Since beans can contain + * lists they need a toolbox to lazy-load their relationships. + * + * @return RedBean_ToolBox $toolbox toolbox containing all kinds of goodies + */ + public function getToolbox() { + return RedBean_Facade::$toolbox; + } + + /** + * Fuse connector. + * Gets the model for a bean $bean. + * Allows you to implement your own way to find the + * right model for a bean and to do dependency injection + * etc. + * + * @param RedBean_OODBBean $bean bean + * + * @return type + */ + public function getModelForBean(RedBean_OODBBean $bean) { + $modelName = RedBean_ModelHelper::getModelName( $bean->getMeta('type'), $bean ); + if (!class_exists($modelName)) return null; + $obj = RedBean_ModelHelper::factory($modelName); + $obj->loadBean($bean); + return $obj; + } +} + + +/** + * SimpleModel + * + * @file RedBean/SimpleModel.php + * @desc Part of FUSE + * @author Gabor de Mooij and the RedBeanPHP Team + * @license BSD/GPLv2 + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_SimpleModel { + + /** + * Contains the inner bean. + * @var RedBean_OODBBean + */ + protected $bean; + + /** + * Used by FUSE: the ModelHelper class to connect a bean to a model. + * This method loads a bean in the model. + * + * @param RedBean_OODBBean $bean bean + */ + public function loadBean( RedBean_OODBBean $bean ) { + $this->bean = $bean; + } + + /** + * Magic Getter to make the bean properties available from + * the $this-scope. + * + * @param string $prop property + * + * @return mixed $propertyValue value + */ + public function __get( $prop ) { + return $this->bean->$prop; + } + + /** + * Magic Setter + * + * @param string $prop property + * @param mixed $value value + */ + public function __set( $prop, $value ) { + $this->bean->$prop = $value; + } + + /** + * Isset implementation + * + * @param string $key key + * + * @return + */ + public function __isset($key) { + return (isset($this->bean->$key)); + } + + /** + * Box the bean using the current model. + * + * @return RedBean_SimpleModel $box a bean in a box + */ + public function box() { + return $this; + } + + /** + * Unbox the bean from the model. + * + * @return RedBean_OODBBean $bean bean + */ + public function unbox(){ + return $this->bean; + } + +} + +/** + * RedBean Model Helper + * + * @file RedBean/ModelHelper.php + * @desc Connects beans to models, in essence + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * This is the core of so-called FUSE. + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + * + */ +class RedBean_ModelHelper implements RedBean_Observer { + + /** + * Holds a model formatter + * @var RedBean_IModelFormatter + */ + private static $modelFormatter; + + + /** + * Holds a dependency injector + * @var type + */ + private static $dependencyInjector; + + /** + * Cache for model names to avoid unnecessary + * reflections. + * + * @var array + */ + private static $modelCache = array(); + + + /** + * Connects OODB to a model if a model exists for that + * type of bean. This connector is used in the facade. + * + * @param string $eventName + * @param RedBean_OODBBean $bean + */ + public function onEvent( $eventName, $bean ) { + $bean->$eventName(); + } + + + /** + * Given a model ID (model identifier) this method returns the + * full model name. + * + * @param string $model + * @param RedBean_OODBBean $bean + * + * @return string $fullname + */ + public static function getModelName( $model, $bean = null ) { + if (isset(self::$modelCache[$model])) return self::$modelCache[$model]; + if (self::$modelFormatter){ + $modelID = self::$modelFormatter->formatModel($model,$bean); + } + else { + $modelID = 'Model_'.ucfirst($model); + } + self::$modelCache[$model] = $modelID; + return self::$modelCache[$model]; + } + + /** + * Sets the model formatter to be used to discover a model + * for Fuse. + * + * @param string $modelFormatter + */ + public static function setModelFormatter( $modelFormatter ) { + self::$modelFormatter = $modelFormatter; + } + + + /** + * Obtains a new instance of $modelClassName, using a dependency injection + * container if possible. + * + * @param string $modelClassName name of the model + */ + public static function factory( $modelClassName ) { + if (self::$dependencyInjector) { + return self::$dependencyInjector->getInstance($modelClassName); + } + return new $modelClassName(); + } + + /** + * Sets the dependency injector to be used. + * + * @param RedBean_DependencyInjector $di injecto to be used + */ + public static function setDependencyInjector( RedBean_DependencyInjector $di ) { + self::$dependencyInjector = $di; + } + + /** + * Stops the dependency injector from resolving dependencies. Removes the + * reference to the dependency injector. + */ + public static function clearDependencyInjector() { + self::$dependencyInjector = null; + } + + /** + * Attaches the FUSE event listeners. Now the Model Helper will listen for + * CRUD events. If a CRUD event occurs it will send a signal to the model + * that belongs to the CRUD bean and this model will take over control from + * there. + * + * @param Observable $observable + */ + public function attachEventListeners( RedBean_Observable $observable ) { + $observable->addEventListener('update', $this ); + $observable->addEventListener('open', $this ); + $observable->addEventListener('delete', $this ); + $observable->addEventListener('after_delete', $this ); + $observable->addEventListener('after_update', $this ); + $observable->addEventListener('dispense', $this ); + } + +} + +/** + * RedBean SQL Helper + * + * @file RedBean/SQLHelper.php + * @desc Allows you to mix PHP and SQL as if they were one language + * @author Gabor de Mooij and the RedBeanPHP community + * @license BSD/GPLv2 + * + * Allows you to mix PHP and SQL as if they were one language + * + * Simplest case: + * + * $r->now(); //returns SQL time + * + * Another Example: + * + * $f->begin() + * ->select('*') + * ->from('island')->where('id = ? ')->put(1)->get(); + * + * Another example: + * + * $f->begin()->show('tables')->get('col'); + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ + class RedBean_SQLHelper { + + /** + * Holds the database adapter for executing SQL queries. + * @var RedBean_Adapter + */ + protected $adapter; + + /** + * Holds current mode + * @var boolean + */ + protected $capture = false; + + /** + * Holds SQL until now + * @var string + */ + protected $sql = ''; + + /** + * Holds list of parameters for SQL Query + * @var array + */ + protected $params = array(); + + /** + * Constructor + * + * @param RedBean_DBAdapter $adapter database adapter for querying + */ + public function __construct(RedBean_Adapter $adapter) { + $this->adapter = $adapter; + } + + /** + * Magic method to construct SQL query + * + * @param string $funcName name of the next SQL statement/keyword + * @param array $args list of statements to be seperated by commas + * + * @return mixed $result either self or result depending on mode + */ + public function __call($funcName,$args=array()) { + $funcName = str_replace('_',' ',$funcName); + if ($this->capture) { + $this->sql .= ' '.$funcName . ' '.implode(',', $args); + return $this; + } + else { + return $this->adapter->getCell('SELECT '.$funcName.'('.implode(',',$args).')'); + } + } + + /** + * Begins SQL query + * + * @return RedBean_SQLHelper $this chainable + */ + public function begin() { + $this->capture = true; + return $this; + } + + /** + * Adds a value to the parameter list + * + * @param mixed $param parameter to be added + * + * @return RedBean_SQLHelper $this chainable + */ + public function put($param) { + $this->params[] = $param; + return $this; + } + + /** + * Executes query and returns result + * + * @return mixed $result + */ + public function get($what='') { + $what = 'get'.ucfirst($what); + $rs = $this->adapter->$what($this->sql,$this->params); + $this->clear(); + return $rs; + } + + /** + * Clears the parameter list as well as the SQL query string. + * + * @return RedBean_SQLHelper $this chainable + */ + public function clear() { + $this->sql = ''; + $this->params = array(); + $this->capture = false; //turn off capture mode (issue #142) + return $this; + } + + /** + * To explicitly add a piece of SQL. + * + * @param string $sql sql + * + * @return RedBean_SQLHelper + */ + public function addSQL($sql) { + if ($this->capture) { + $this->sql .= ' '.$sql . ' '; + return $this; + } + } + + + /** + * Returns query parts. + * + * @return array $queryParts query parts. + */ + public function getQuery() { + $list = array($this->sql,$this->params); + $this->clear(); + return $list; + } + + /** + * Writes a '(' to the sql query. + */ + public function open() { + if ($this->capture) { + $this->sql .= ' ( '; + return $this; + } + } + + /** + * Writes a ')' to the sql query. + */ + public function close() { + if ($this->capture) { + $this->sql .= ' ) '; + return $this; + } + } + +} + +/** + * RedBean Tag Manager + * + * @file RedBean/TagManager.php + * @desc RedBean Tag Manager + * @author Gabor de Mooij and the RedBeanPHP community + * @license BSD/GPLv2 + * + * Provides methods to tag beans and perform tag-based searches in the + * bean database. + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_TagManager { + + /** + * The Tag Manager requires a toolbox + * @var RedBean_Toolbox + */ + protected $toolbox; + + /** + * Association Manager to manage tag-bean relations + * @var RedBean_AssociationManager + */ + protected $associationManager; + + /** + * RedBeanPHP OODB instance + * @var RedBean_OODBBean + */ + protected $redbean; + + /** + * Constructor, + * creates a new instance of TagManager. + * @param RedBean_Toolbox $toolbox + */ + public function __construct( RedBean_Toolbox $toolbox ) { + $this->toolbox = $toolbox; + $this->redbean = $toolbox->getRedBean(); + $this->associationManager = $this->redbean->getAssociationManager(); + } + + /** + * Finds a tag bean by it's title. + * + * @param string $title title + * + * @return RedBean_OODBBean $bean | null + */ + public function findTagByTitle($title) { + $beans = $this->redbean->find('tag',array('title'=>array($title))); + if ($beans) { + $bean = reset($beans); + return $bean; + } + return null; + } + + /** + * Part of RedBeanPHP Tagging API. + * Tests whether a bean has been associated with one ore more + * of the listed tags. If the third parameter is TRUE this method + * will return TRUE only if all tags that have been specified are indeed + * associated with the given bean, otherwise FALSE. + * If the third parameter is FALSE this + * method will return TRUE if one of the tags matches, FALSE if none + * match. + * + * @param RedBean_OODBBean $bean bean to check for tags + * @param array $tags list of tags + * @param boolean $all whether they must all match or just some + * + * @return boolean $didMatch whether the bean has been assoc. with the tags + */ + public function hasTag($bean, $tags, $all=false) { + $foundtags = $this->tag($bean); + if (is_string($foundtags)) $foundtags = explode(",",$tags); + $same = array_intersect($tags,$foundtags); + if ($all) { + return (implode(",",$same)===implode(",",$tags)); + } + return (bool) (count($same)>0); + } + + /** + * Part of RedBeanPHP Tagging API. + * Removes all sepcified tags from the bean. The tags specified in + * the second parameter will no longer be associated with the bean. + * + * @param RedBean_OODBBean $bean tagged bean + * @param array $tagList list of tags (names) + * + * @return void + */ + public function untag($bean,$tagList) { + if ($tagList!==false && !is_array($tagList)) $tags = explode( ",", (string)$tagList); else $tags=$tagList; + foreach($tags as $tag) { + if ($t = $this->findTagByTitle($tag)) { + $this->associationManager->unassociate( $bean, $t ); + } + } + } + + /** + * Part of RedBeanPHP Tagging API. + * Tags a bean or returns tags associated with a bean. + * If $tagList is null or omitted this method will return a + * comma separated list of tags associated with the bean provided. + * If $tagList is a comma separated list (string) of tags all tags will + * be associated with the bean. + * You may also pass an array instead of a string. + * + * @param RedBean_OODBBean $bean bean + * @param mixed $tagList tags + * + * @return string $commaSepListTags + */ + public function tag( RedBean_OODBBean $bean, $tagList = null ) { + if (is_null($tagList)) { + $tags = array(); + $keys = $this->associationManager->related($bean, 'tag'); + if ($keys) { + $tags = $this->redbean->batch('tag',$keys); + } + $foundTags = array(); + foreach($tags as $tag) { + $foundTags[] = $tag->title; + } + return $foundTags; + } + $this->associationManager->clearRelations( $bean, 'tag' ); + $this->addTags( $bean, $tagList ); + } + + /** + * Part of RedBeanPHP Tagging API. + * Adds tags to a bean. + * If $tagList is a comma separated list of tags all tags will + * be associated with the bean. + * You may also pass an array instead of a string. + * + * @param RedBean_OODBBean $bean bean + * @param array $tagList list of tags to add to bean + * + * @return void + */ + public function addTags( RedBean_OODBBean $bean, $tagList ) { + if ($tagList!==false && !is_array($tagList)) $tags = explode( ",", (string)$tagList); else $tags=$tagList; + if ($tagList===false) return; + foreach($tags as $tag) { + if (!$t = $this->findTagByTitle($tag)) { + $t = $this->redbean->dispense('tag'); + $t->title = $tag; + $this->redbean->store($t); + } + $this->associationManager->associate( $bean, $t ); + } + } + + /** + * Part of RedBeanPHP Tagging API. + * Returns all beans that have been tagged with one of the tags given. + * + * @param $beanType type of bean you are looking for + * @param $tagList list of tags to match + * + * @return array + */ + public function tagged( $beanType, $tagList ) { + if ($tagList!==false && !is_array($tagList)) $tags = explode( ",", (string)$tagList); else $tags=$tagList; + $collection = array(); + $tags = $this->redbean->find('tag',array('title'=>$tags)); + if (is_array($tags) && count($tags)>0) { + $collectionKeys = $this->associationManager->related($tags,$beanType); + if ($collectionKeys) { + $collection = $this->redbean->batch($beanType,$collectionKeys); + } + } + return $collection; + } + + /** + * Part of RedBeanPHP Tagging API. + * Returns all beans that have been tagged with ALL of the tags given. + * + * @param $beanType type of bean you are looking for + * @param $tagList list of tags to match + * + * @return array + */ + public function taggedAll( $beanType, $tagList ) { + if ($tagList!==false && !is_array($tagList)) $tags = explode( ",", (string)$tagList); else $tags=$tagList; + $beans = array(); + foreach($tags as $tag) { + $beans = $this->tagged($beanType,$tag); + if (isset($oldBeans)) $beans = array_intersect_assoc($beans,$oldBeans); + $oldBeans = $beans; + } + return $beans; + } + +} + +/** + * RedBean Facade + * @file RedBean/Facade.php + * @desc Convenience class for RedBeanPHP. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * This class hides the object landscape of + * RedBeanPHP behind a single letter class providing + * almost all functionality with simple static calls. + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + * + */ +class RedBean_Facade { + + /** + * Collection of toolboxes + * @var array + */ + public static $toolboxes = array(); + /** + * + * Constains an instance of the RedBean Toolbox + * @var RedBean_ToolBox + * + */ + public static $toolbox; + + /** + * Constains an instance of RedBean OODB + * @var RedBean_OODB + */ + public static $redbean; + + /** + * Contains an instance of the Query Writer + * @var RedBean_QueryWriter + */ + public static $writer; + + /** + * Contains an instance of the Database + * Adapter. + * @var RedBean_DBAdapter + */ + public static $adapter; + + + /** + * Contains an instance of the Association Manager + * @var RedBean_AssociationManager + */ + public static $associationManager; + + + /** + * Contains an instance of the Extended Association Manager + * @var RedBean_ExtAssociationManager + */ + public static $extAssocManager; + + /** + * Holds the tag manager + * @var RedBean_TagManager + */ + public static $tagManager; + + /** + * holds the duplication manager + * @var RedBean_DuplicationManager + */ + public static $duplicationManager; + + /** + * Holds the Key of the current database. + * @var string + */ + public static $currentDB = ''; + + /** + * Holds reference to SQL Helper + */ + public static $f; + + + private static $strictType = true; + + + /** + * Get version + * @return string + */ + public static function getVersion() { + return '3.3'; + } + + /** + * Kickstarts redbean for you. This method should be called before you start using + * RedBean. The Setup() method can be called without any arguments, in this case it will + * try to create a SQLite database in /tmp called red.db (this only works on UNIX-like systems). + * + * @param string $dsn Database connection string + * @param string $username Username for database + * @param string $password Password for database + * + * @return void + */ + public static function setup( $dsn=NULL, $username=NULL, $password=NULL ) { + if (function_exists('sys_get_temp_dir')) $tmp = sys_get_temp_dir(); else $tmp = 'tmp'; + if (is_null($dsn)) $dsn = 'sqlite:/'.$tmp.'/red.db'; + self::addDatabase('default',$dsn,$username,$password); + self::selectDatabase('default'); + return self::$toolbox; + } + + + /** + * Adds a database to the facade, afterwards you can select the database using + * selectDatabase($key). + * + * @param string $key ID for the database + * @param string $dsn DSN for the database + * @param string $user User for connection + * @param null|string $pass Password for connection + * @param bool $frozen Whether this database is frozen or not + * + * @return void + */ + public static function addDatabase( $key, $dsn, $user=null, $pass=null, $frozen=false ) { + self::$toolboxes[$key] = RedBean_Setup::kickstart($dsn,$user,$pass,$frozen); + } + + + /** + * Selects a different database for the Facade to work with. + * + * @param string $key Key of the database to select + * @return int 1 + */ + public static function selectDatabase($key) { + if (self::$currentDB===$key) return false; + self::configureFacadeWithToolbox(self::$toolboxes[$key]); + self::$currentDB = $key; + return true; + } + + + /** + * Toggles DEBUG mode. + * In Debug mode all SQL that happens under the hood will + * be printed to the screen or logged by provided logger. + * + * @param boolean $tf + * @param RedBean_Logger $logger + */ + public static function debug( $tf = true, $logger = NULL ) { + if (!$logger) $logger = new RedBean_Logger_Default; + self::$adapter->getDatabase()->setDebugMode( $tf, $logger ); + } + + /** + * Stores a RedBean OODB Bean and returns the ID. + * + * @param RedBean_OODBBean|RedBean_SimpleModel $bean bean + * + * @return integer $id id + */ + public static function store( $bean ) { + return self::$redbean->store( $bean ); + } + + + /** + * Toggles fluid or frozen mode. In fluid mode the database + * structure is adjusted to accomodate your objects. In frozen mode + * this is not the case. + * + * You can also pass an array containing a selection of frozen types. + * Let's call this chilly mode, it's just like fluid mode except that + * certain types (i.e. tables) aren't touched. + * + * @param boolean|array $trueFalse + */ + public static function freeze( $tf = true ) { + self::$redbean->freeze( $tf ); + } + + + /** + * Loads the bean with the given type and id and returns it. + * + * @param string $type type + * @param integer $id id of the bean you want to load + * + * @return RedBean_OODBBean $bean + */ + public static function load( $type, $id ) { + return self::$redbean->load( $type, $id ); + } + + /** + * Deletes the specified bean. + * + * @param RedBean_OODBBean|RedBean_SimpleModel $bean bean to be deleted + * + * @return mixed + */ + public static function trash( $bean ) { + return self::$redbean->trash( $bean ); + } + + /** + * Dispenses a new RedBean OODB Bean for use with + * the rest of the methods. + * + * @param string $type type + * @param integer $number number of beans to dispense + * + * @return array $oneOrMoreBeans + */ + public static function dispense( $type, $num = 1 ) { + if (!preg_match('/^[a-z0-9]+$/',$type) && self::$strictType) throw new RedBean_Exception_Security('Invalid type: '.$type); + if ($num==1) { + return self::$redbean->dispense( $type ); + } + else { + $beans = array(); + for($v=0; $v<$num; $v++) $beans[] = self::$redbean->dispense( $type ); + return $beans; + } + } + + /** + * Toggles strict bean type names. + * If set to true (default) this will forbid the use of underscores and + * uppercase characters in bean type strings (R::dispense). + * + * @param boolean $trueFalse + */ + public static function setStrictTyping($trueFalse) { + self::$strictType = (boolean) $trueFalse; + } + + /** + * Convience method. Tries to find beans of a certain type, + * if no beans are found, it dispenses a bean of that type. + * + * @param string $type type of bean you are looking for + * @param string $sql SQL code for finding the bean + * @param array $values parameters to bind to SQL + * + * @return array $beans Contains RedBean_OODBBean instances + */ + public static function findOrDispense( $type, $sql, $values ) { + $foundBeans = self::find($type,$sql,$values); + if (count($foundBeans)==0) return array(self::dispense($type)); else return $foundBeans; + } + + /** + * Associates two Beans. This method will associate two beans with eachother. + * You can then get one of the beans by using the related() function and + * providing the other bean. You can also provide a base bean in the extra + * parameter. This base bean allows you to add extra information to the association + * record. Note that this is for advanced use only and the information will not + * be added to one of the beans, just to the association record. + * It's also possible to provide an array or JSON string as base bean. If you + * pass a scalar this function will interpret the base bean as having one + * property called 'extra' with the value of the scalar. + * + * @param RedBean_OODBBean $bean1 bean that will be part of the association + * @param RedBean_OODBBean $bean2 bean that will be part of the association + * @param mixed $extra bean, scalar, array or JSON providing extra data. + * + * @return mixed + */ + public static function associate( $beans1, $beans2, $extra = null ) { + //No extra? Just associate like always (default) + if (!$extra) { + return self::$associationManager->associate( $beans1, $beans2 ); + } + else{ + if (!is_array($extra)) { + $info = json_decode($extra,true); + if (!$info) $info = array('extra'=>$extra); + } + else { + $info = $extra; + } + $bean = RedBean_Facade::dispense('xtypeless'); + $bean->import($info); + return self::$extAssocManager->extAssociate($beans1, $beans2, $bean); + } + } + + + /** + * Breaks the association between two beans. + * This functions breaks the association between a pair of beans. After + * calling this functions the beans will no longer be associated with + * eachother. Calling related() with either one of the beans will no longer + * return the other bean. + * + * @param RedBean_OODBBean $bean1 bean + * @param RedBean_OODBBean $bean2 bean + * + * @return mixed + */ + public static function unassociate( $beans1, $beans2 , $fast=false) { + return self::$associationManager->unassociate( $beans1, $beans2, $fast ); + + } + + /** + * Returns all the beans associated with $bean. + * This method will return an array containing all the beans that have + * been associated once with the associate() function and are still + * associated with the bean specified. The type parameter indicates the + * type of beans you are looking for. You can also pass some extra SQL and + * values for that SQL to filter your results after fetching the + * related beans. + * + * Dont try to make use of subqueries, a subquery using IN() seems to + * be slower than two queries! + * + * Since 3.2, you can now also pass an array of beans instead just one + * bean as the first parameter. + * + * @param RedBean_OODBBean|array $bean the bean you have + * @param string $type the type of beans you want + * @param string $sql SQL snippet for extra filtering + * @param array $val values to be inserted in SQL slots + * + * @return array $beans beans yielded by your query. + */ + public static function related( $bean, $type, $sql=null, $values=array()) { + $keys = self::$associationManager->related( $bean, $type ); + if (count($keys)==0 || !is_array($keys)) return array(); + if (!$sql) return self::batch($type, $keys); + $rows = self::$writer->selectRecord( $type, array('id'=>$keys),array($sql,$values),false ); + return self::$redbean->convertToBeans($type,$rows); + } + + /** + * Returns only single associated bean. + * + * @param RedBean_OODBBean $bean bean provided + * @param string $type type of bean you are searching for + * @param string $sql SQL for extra filtering + * @param array $values values to be inserted in SQL slots + * + * + * @return RedBean_OODBBean $bean + */ + public static function relatedOne( RedBean_OODBBean $bean, $type, $sql=null, $values=array() ) { + $beans = self::related($bean, $type, $sql, $values); + if (count($beans)==0 || !is_array($beans)) return null; + return reset( $beans ); + } + + /** + * Checks whether a pair of beans is related N-M. This function does not + * check whether the beans are related in N:1 way. + * + * @param RedBean_OODBBean $bean1 first bean + * @param RedBean_OODBBean $bean2 second bean + * + * @return bool $yesNo whether they are related + */ + public static function areRelated( RedBean_OODBBean $bean1, RedBean_OODBBean $bean2) { + return self::$associationManager->areRelated($bean1,$bean2); + } + + + /** + * The opposite of related(). Returns all the beans that are not + * associated with the bean provided. + * + * @param RedBean_OODBBean $bean bean provided + * @param string $type type of bean you are searching for + * @param string $sql SQL for extra filtering + * @param array $values values to be inserted in SQL slots + * + * @return array $beans beans + */ + public static function unrelated(RedBean_OODBBean $bean, $type, $sql=null, $values=array()) { + $keys = self::$associationManager->related( $bean, $type ); + $rows = self::$writer->selectRecord( $type, array('id'=>$keys), array($sql,$values), false, true ); + return self::$redbean->convertToBeans($type,$rows); + + } + + + + /** + * Clears all associated beans. + * Breaks all many-to-many associations of a bean and a specified type. + * + * @param RedBean_OODBBean $bean bean you wish to clear many-to-many relations for + * @param string $type type of bean you wish to break associatons with + * + * @return void + */ + public static function clearRelations( RedBean_OODBBean $bean, $type ) { + self::$associationManager->clearRelations( $bean, $type ); + } + + /** + * Finds a bean using a type and a where clause (SQL). + * As with most Query tools in RedBean you can provide values to + * be inserted in the SQL statement by populating the value + * array parameter; you can either use the question mark notation + * or the slot-notation (:keyname). + * + * @param string $type type the type of bean you are looking for + * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause + * @param array $values values array of values to be bound to parameters in query + * + * @return array $beans beans + */ + public static function find( $type, $sql=null, $values=array() ) { + if ($sql instanceof RedBean_SQLHelper) list($sql,$values) = $sql->getQuery(); + if (!is_array($values)) throw new InvalidArgumentException('Expected array, ' . gettype($values) . ' given.'); + return self::$redbean->find($type,array(),array($sql,$values)); + } + + /** + * Finds a bean using a type and a where clause (SQL). + * As with most Query tools in RedBean you can provide values to + * be inserted in the SQL statement by populating the value + * array parameter; you can either use the question mark notation + * or the slot-notation (:keyname). + * The findAll() method differs from the find() method in that it does + * not assume a WHERE-clause, so this is valid: + * + * R::findAll('person',' ORDER BY name DESC '); + * + * Your SQL does not have to start with a valid WHERE-clause condition. + * + * @param string $type type the type of bean you are looking for + * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause + * @param array $values values array of values to be bound to parameters in query + * + * @return array $beans beans + */ + public static function findAll( $type, $sql=null, $values=array() ) { + if (!is_array($values)) throw new InvalidArgumentException('Expected array, ' . gettype($values) . ' given.'); + return self::$redbean->find($type,array(),array($sql,$values),true); + } + + /** + * Finds a bean using a type and a where clause (SQL). + * As with most Query tools in RedBean you can provide values to + * be inserted in the SQL statement by populating the value + * array parameter; you can either use the question mark notation + * or the slot-notation (:keyname). + * The variation also exports the beans (i.e. it returns arrays). + * + * @param string $type type the type of bean you are looking for + * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause + * @param array $values values array of values to be bound to parameters in query + * + * @return array $arrays arrays + */ + public static function findAndExport($type, $sql=null, $values=array()) { + $items = self::find( $type, $sql, $values ); + $arr = array(); + foreach($items as $key=>$item) { + $arr[$key]=$item->export(); + } + return $arr; + } + + /** + * Finds a bean using a type and a where clause (SQL). + * As with most Query tools in RedBean you can provide values to + * be inserted in the SQL statement by populating the value + * array parameter; you can either use the question mark notation + * or the slot-notation (:keyname). + * This variation returns the first bean only. + * + * @param string $type type the type of bean you are looking for + * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause + * @param array $values values array of values to be bound to parameters in query + * + * @return RedBean_OODBBean $bean + */ + public static function findOne( $type, $sql=null, $values=array()) { + $items = self::find($type,$sql,$values); + $found = reset($items); + if (!$found) return null; + return $found; + } + + /** + * Finds a bean using a type and a where clause (SQL). + * As with most Query tools in RedBean you can provide values to + * be inserted in the SQL statement by populating the value + * array parameter; you can either use the question mark notation + * or the slot-notation (:keyname). + * This variation returns the last bean only. + * + * @param string $type type the type of bean you are looking for + * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause + * @param array $values values array of values to be bound to parameters in query + * + * @return RedBean_OODBBean $bean + */ + public static function findLast( $type, $sql=null, $values=array() ) { + $items = self::find( $type, $sql, $values ); + $found = end( $items ); + if (!$found) return null; + return $found; + } + + /** + * Returns an array of beans. Pass a type and a series of ids and + * this method will bring you the correspondig beans. + * + * important note: Because this method loads beans using the load() + * function (but faster) it will return empty beans with ID 0 for + * every bean that could not be located. The resulting beans will have the + * passed IDs as their keys. + * + * @param string $type type of beans + * @param array $ids ids to load + * + * @return array $beans resulting beans (may include empty ones) + */ + public static function batch( $type, $ids ) { + return self::$redbean->batch($type, $ids); + } + + /** + * Convenience function to execute Queries directly. + * Executes SQL. + * + * @param string $sql sql SQL query to execute + * @param array $values values a list of values to be bound to query parameters + * + * @return integer $affected number of affected rows + */ + public static function exec( $sql, $values=array() ) { + return self::query('exec',$sql,$values); + } + + /** + * Convenience function to execute Queries directly. + * Executes SQL. + * + * @param string $sql sql SQL query to execute + * @param array $values values a list of values to be bound to query parameters + * + * @return array $results + */ + public static function getAll( $sql, $values=array() ) { + return self::query('get',$sql,$values); + } + + /** + * Convenience function to execute Queries directly. + * Executes SQL. + * + * @param string $sql sql SQL query to execute + * @param array $values values a list of values to be bound to query parameters + * + * @return string $result scalar + */ + public static function getCell( $sql, $values=array() ) { + return self::query('getCell',$sql,$values); + } + + /** + * Convenience function to execute Queries directly. + * Executes SQL. + * + * @param string $sql sql SQL query to execute + * @param array $values values a list of values to be bound to query parameters + * + * @return array $results + */ + public static function getRow( $sql, $values=array() ) { + return self::query('getRow',$sql,$values); + } + + /** + * Convenience function to execute Queries directly. + * Executes SQL. + * + * @param string $sql sql SQL query to execute + * @param array $values values a list of values to be bound to query parameters + * + * @return array $results + */ + public static function getCol( $sql, $values=array() ) { + return self::query('getCol',$sql,$values); + } + + /** + * Internal Query function, executes the desired query. Used by + * all facade query functions. This keeps things DRY. + * + * @throws RedBean_Exception_SQL + * + * @param string $method desired query method (i.e. 'cell','col','exec' etc..) + * @param string $sql the sql you want to execute + * @param array $values array of values to be bound to query statement + * + * @return array $results results of query + */ + private static function query($method,$sql,$values) { + if (!self::$redbean->isFrozen()) { + try { + $rs = RedBean_Facade::$adapter->$method( $sql, $values ); + }catch(RedBean_Exception_SQL $e) { + if(self::$writer->sqlStateIn($e->getSQLState(), + array( + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, + RedBean_QueryWriter::C_SQLSTATE_NO_SUCH_TABLE) + )) { + return array(); + } + else { + throw $e; + } + } + return $rs; + } + else { + return RedBean_Facade::$adapter->$method( $sql, $values ); + } + } + + /** + * Convenience function to execute Queries directly. + * Executes SQL. + * Results will be returned as an associative array. The first + * column in the select clause will be used for the keys in this array and + * the second column will be used for the values. If only one column is + * selected in the query, both key and value of the array will have the + * value of this field for each row. + * + * @param string $sql sql SQL query to execute + * @param array $values values a list of values to be bound to query parameters + * + * @return array $results + */ + public static function getAssoc($sql,$values=array()) { + return self::query('getAssoc',$sql,$values); + } + + + /** + * Makes a copy of a bean. This method makes a deep copy + * of the bean.The copy will have the following features. + * - All beans in own-lists will be duplicated as well + * - All references to shared beans will be copied but not the shared beans themselves + * - All references to parent objects (_id fields) will be copied but not the parents themselves + * In most cases this is the desired scenario for copying beans. + * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found + * (i.e. one that already has been processed) the ID of the bean will be returned. + * This should not happen though. + * + * Note: + * This function does a reflectional database query so it may be slow. + * + * @param RedBean_OODBBean $bean bean to be copied + * @param array $trail for internal usage, pass array() + * @param boolean $pid for internal usage + * + * @return array $copiedBean the duplicated bean + */ + public static function dup($bean,$trail=array(),$pid=false,$filters=array()) { + self::$duplicationManager->setFilters($filters); + return self::$duplicationManager->dup($bean, $trail,$pid); + } + + /** + * Exports a collection of beans. Handy for XML/JSON exports with a + * Javascript framework like Dojo or ExtJS. + * What will be exported: + * - contents of the bean + * - all own bean lists (recursively) + * - all shared beans (not THEIR own lists) + * + * @param array|RedBean_OODBBean $beans beans to be exported + * + * @return array $array exported structure + */ + public static function exportAll($beans,$parents=false,$filters=array()) { + $array = array(); + if (!is_array($beans)) $beans = array($beans); + foreach($beans as $bean) { + $f = self::dup($bean,array(),true,$filters); + $array[] = $f->export(false,$parents,false,$filters); + } + return $array; + } + + + /** + * Given an array of two beans and a property, this method + * swaps the value of the property. + * This is handy if you need to swap the priority or orderNo + * of an item (i.e. bug-tracking, page order). + * + * @param array $beans beans + * @param string $property property + */ + public static function swap( $beans, $property ) { + $bean1 = array_shift($beans); + $bean2 = array_shift($beans); + $tmp = $bean1->$property; + $bean1->$property = $bean2->$property; + $bean2->$property = $tmp; + RedBean_Facade::store($bean1); + RedBean_Facade::store($bean2); + } + + /** + * Converts a series of rows to beans. + * + * @param string $type type + * @param array $rows must contain an array of arrays. + * + * @return array $beans + */ + public static function convertToBeans($type,$rows) { + return self::$redbean->convertToBeans($type,$rows); + } + + + /** + * Part of RedBeanPHP Tagging API. + * Tests whether a bean has been associated with one ore more + * of the listed tags. If the third parameter is TRUE this method + * will return TRUE only if all tags that have been specified are indeed + * associated with the given bean, otherwise FALSE. + * If the third parameter is FALSE this + * method will return TRUE if one of the tags matches, FALSE if none + * match. + * + * @param RedBean_OODBBean $bean bean to check for tags + * @param array $tags list of tags + * @param boolean $all whether they must all match or just some + * + * @return boolean $didMatch whether the bean has been assoc. with the tags + */ + public static function hasTag($bean, $tags, $all=false) { + return self::$tagManager->hasTag($bean,$tags,$all); + } + + /** + * Part of RedBeanPHP Tagging API. + * Removes all sepcified tags from the bean. The tags specified in + * the second parameter will no longer be associated with the bean. + * + * @param RedBean_OODBBean $bean tagged bean + * @param array $tagList list of tags (names) + * + * @return void + */ + public static function untag($bean,$tagList) { + return self::$tagManager->untag($bean,$tagList); + } + + /** + * Part of RedBeanPHP Tagging API. + * Tags a bean or returns tags associated with a bean. + * If $tagList is null or omitted this method will return a + * comma separated list of tags associated with the bean provided. + * If $tagList is a comma separated list (string) of tags all tags will + * be associated with the bean. + * You may also pass an array instead of a string. + * + * @param RedBean_OODBBean $bean bean + * @param mixed $tagList tags + * + * @return string $commaSepListTags + */ + public static function tag( RedBean_OODBBean $bean, $tagList = null ) { + return self::$tagManager->tag($bean,$tagList); + } + + /** + * Part of RedBeanPHP Tagging API. + * Adds tags to a bean. + * If $tagList is a comma separated list of tags all tags will + * be associated with the bean. + * You may also pass an array instead of a string. + * + * @param RedBean_OODBBean $bean bean + * @param array $tagList list of tags to add to bean + * + * @return void + */ + public static function addTags( RedBean_OODBBean $bean, $tagList ) { + return self::$tagManager->addTags($bean,$tagList); + } + + /** + * Part of RedBeanPHP Tagging API. + * Returns all beans that have been tagged with one of the tags given. + * + * @param $beanType type of bean you are looking for + * @param $tagList list of tags to match + * + * @return array + */ + public static function tagged( $beanType, $tagList ) { + return self::$tagManager->tagged($beanType,$tagList); + } + + /** + * Part of RedBeanPHP Tagging API. + * Returns all beans that have been tagged with ALL of the tags given. + * + * @param $beanType type of bean you are looking for + * @param $tagList list of tags to match + * + * @return array + */ + public static function taggedAll( $beanType, $tagList ) { + return self::$tagManager->taggedAll($beanType,$tagList); + } + + + /** + * Wipes all beans of type $beanType. + * + * @param string $beanType type of bean you want to destroy entirely. + */ + public static function wipe( $beanType ) { + return RedBean_Facade::$redbean->wipe($beanType); + } + + /** + * Counts beans + * + * @param string $beanType type of bean + * @param string $addSQL additional SQL snippet (for filtering, limiting) + * @param array $params parameters to bind to SQL + * + * @return integer $numOfBeans + */ + + public static function count( $beanType, $addSQL = '', $params = array() ) { + return RedBean_Facade::$redbean->count($beanType,$addSQL,$params); + } + + /** + * Configures the facade, want to have a new Writer? A new Object Database or a new + * Adapter and you want it on-the-fly? Use this method to hot-swap your facade with a new + * toolbox. + * + * @param RedBean_ToolBox $tb toolbox + * + * @return RedBean_ToolBox $tb old, rusty, previously used toolbox + */ + public static function configureFacadeWithToolbox( RedBean_ToolBox $tb ) { + $oldTools = self::$toolbox; + self::$toolbox = $tb; + self::$writer = self::$toolbox->getWriter(); + self::$adapter = self::$toolbox->getDatabaseAdapter(); + self::$redbean = self::$toolbox->getRedBean(); + self::$associationManager = new RedBean_AssociationManager( self::$toolbox ); + self::$redbean->setAssociationManager(self::$associationManager); + self::$extAssocManager = new RedBean_AssociationManager_ExtAssociationManager( self::$toolbox ); + $helper = new RedBean_ModelHelper(); + $helper->attachEventListeners(self::$redbean); + self::$associationManager->addEventListener('delete', $helper ); + self::$duplicationManager = new RedBean_DuplicationManager(self::$toolbox); + self::$tagManager = new RedBean_TagManager( self::$toolbox ); + self::$f = new RedBean_SQLHelper(self::$adapter); + return $oldTools; + } + + /** + * Facade Convience method for adapter transaction system. + * Begins a transaction. + * + * @return void + */ + public static function begin() { + self::$adapter->startTransaction(); + } + + /** + * Facade Convience method for adapter transaction system. + * Commits a transaction. + * + * @return void + */ + public static function commit() { + self::$adapter->commit(); + } + + /** + * Facade Convience method for adapter transaction system. + * Rolls back a transaction. + * + * @return void + */ + public static function rollback() { + self::$adapter->rollback(); + } + + /** + * Returns a list of columns. Format of this array: + * array( fieldname => type ) + * Note that this method only works in fluid mode because it might be + * quite heavy on production servers! + * + * @param string $table name of the table (not type) you want to get columns of + * + * @return array $columns list of columns and their types + */ + public static function getColumns($table) { + return self::$writer->getColumns($table); + } + + /** + * Generates question mark slots for an array of values. + * + * @param array $array + * @return string $slots + */ + public static function genSlots($array) { + if (is_array($array) && count($array)>0) { + $filler = array_fill(0,count($array),'?'); + return implode(',',$filler); + } + else { + return ''; + } + } + + /** + * Nukes the entire database. + */ + public static function nuke() { + if (!self::$redbean->isFrozen()) { + self::$writer->wipeAll(); + } + } + + /** + * Sets a list of dependencies. + * A dependency list contains an entry for each dependent bean. + * A dependent bean will be removed if the relation with one of the + * dependencies gets broken. + * + * Example: + * + * array( + * 'page' => array('book','magazine') + * ) + * + * A page will be removed if: + * + * unset($book->ownPage[$pageID]); + * + * or: + * + * unset($magazine->ownPage[$pageID]); + * + * but not if: + * + * unset($paper->ownPage[$pageID]); + * + * + * @param array $dep list of dependencies + */ + public static function dependencies($dep) { + self::$redbean->setDepList($dep); + } + + /** + * Short hand function to store a set of beans at once, IDs will be + * returned as an array. For information please consult the R::store() + * function. + * A loop saver. + * + * @param array $beans list of beans to be stored + * + * @return array $ids list of resulting IDs + */ + public static function storeAll($beans) { + $ids = array(); + foreach($beans as $bean) $ids[] = self::store($bean); + return $ids; + } + + /** + * Short hand function to trash a set of beans at once. + * For information please consult the R::trash() function. + * A loop saver. + * + * @param array $beans list of beans to be trashed + */ + public static function trashAll($beans) { + foreach($beans as $bean) self::trash($bean); + } + + /** + * A label is a bean with only an id, type and name property. + * This function will dispense beans for all entries in the array. The + * values of the array will be assigned to the name property of each + * individual bean. + * + * @param string $type type of beans you would like to have + * @param array $labels list of labels, names for each bean + * + * @return array $bean a list of beans with type and name property + */ + public static function dispenseLabels($type,$labels) { + $labelBeans = array(); + foreach($labels as $label) { + $labelBean = self::dispense($type); + $labelBean->name = $label; + $labelBeans[] = $labelBean; + } + return $labelBeans; + } + + /** + * Gathers labels from beans. This function loops through the beans, + * collects the values of the name properties of each individual bean + * and stores the names in a new array. The array then gets sorted using the + * default sort function of PHP (sort). + * + * @param array $beans list of beans to loop + * + * @return array $array list of names of beans + */ + public static function gatherLabels($beans) { + $labels = array(); + foreach($beans as $bean) $labels[] = $bean->name; + sort($labels); + return $labels; + } + + + /** + * Closes the database connection. + */ + public static function close() { + if (isset(self::$adapter)){ + self::$adapter->close(); + } + } + + + + + /** + * Simple convenience function, returns ISO date formatted representation + * of $time. + * + * @param mixed $time UNIX timestamp + * + * @return type + */ + public static function isoDate( $time = null ) { + if (!$time) $time = time(); + return @date('Y-m-d',$time); + } + + /** + * Simple convenience function, returns ISO date time + * formatted representation + * of $time. + * + * @param mixed $time UNIX timestamp + * + * @return type + */ + public static function isoDateTime( $time = null) { + if (!$time) $time = time(); + return @date('Y-m-d H:i:s',$time); + } + + /** + * Optional accessor for neat code. + * Sets the database adapter you want to use. + * + * @param RedBean_Adapter $adapter + */ + public static function setDatabaseAdapter(RedBean_Adapter $adapter) { + self::$adapter = $adapter; + } + + /** + * Optional accessor for neat code. + * Sets the database adapter you want to use. + * + * @param RedBean_QueryWriter $writer + */ + public static function setWriter(RedBean_QueryWriter $writer) { + self::$writer = $writer; + } + + /** + * Optional accessor for neat code. + * Sets the database adapter you want to use. + * + * @param RedBean_OODB $redbean + */ + public static function setRedBean(RedBean_OODB $redbean) { + self::$redbean = $redbean; + } + + /** + * Optional accessor for neat code. + * Sets the database adapter you want to use. + * + * @return RedBean_DatabaseAdapter $adapter + */ + public static function getDatabaseAdapter() { + return self::$adapter; + } + + /** + * Optional accessor for neat code. + * Sets the database adapter you want to use. + * + * @return RedBean_QueryWriter $writer + */ + public static function getWriter() { + return self::$writer; + } + + /** + * Optional accessor for neat code. + * Sets the database adapter you want to use. + * + * @return RedBean_RedBean $redbean + */ + public static function getRedBean() { + return self::$redbean; + } + + /** + * Preloads certain properties for beans. + * Understands aliases. + * + * Usage: R::preload($books,array('coauthor'=>'author')); + * + * @param array $beans beans + * @param array $types types to load + */ + public static function preload($beans,$types) { + return self::$redbean->preload($beans,$types); + } + +} + +//Compatibility with PHP 5.2 and earlier +function __lcfirst( $str ){ return (string)(strtolower(substr($str,0,1)).substr($str,1)); } + + +/** + * RedBean Plugin + * + * @file RedBean/Plugin.php + * @desc Marker interface for plugins. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + * + */ +interface RedBean_Plugin { }; + +/** + * Sync + * + * @file RedBean/Plugin/Sync.php + * @desc Plugin for Synchronizing databases. + * + * @plugin public static function syncSchema($from,$to) { return RedBean_Plugin_Sync::syncSchema($from,$to); } + * + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * + * (c) G.J.G.T. (Gabor) de Mooij + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_Plugin_Sync implements RedBean_Plugin { + + /** + * Captures the SQL required to adjust source database to match + * schema of target database and feeds this sql code to the + * adapter of the target database. + * + * @param RedBean_Toolbox $source toolbox of source database + * @param RedBean_Toolbox $target toolbox of target database + */ + public function doSync(RedBean_Toolbox $source, RedBean_Toolbox $target) { + + $sourceWriter = $source->getWriter(); + $targetWriter = $target->getWriter(); + + $longText = str_repeat('lorem ipsum', 9000); + $testmap = array( + false, 1, 2.5, -10, 1000, 'abc', $longText, '2010-10-10', '2010-10-10 10:00:00', '10:00:00', 'POINT(1 2)' + ); + $translations = array(); + $defaultCode = $targetWriter->scanType('string'); + foreach ($testmap as $v) { + $code = $sourceWriter->scanType($v, true); + $translation = $targetWriter->scanType($v, true); + if (!isset($translations[$code])) + $translations[$code] = $translation; + if ($translation > $translations[$code] && $translation < 50) + $translations[$code] = $translation; + } + + + //Fix narrow translations SQLiteT stores date as double. (double != really double) + if (get_class($sourceWriter)==='RedBean_QueryWriter_SQLiteT') { + $translations[1] = $defaultCode; //use magic number in case writer not loaded. + } + + $sourceTables = $sourceWriter->getTables(); + $targetTables = $targetWriter->getTables(); + $missingTables = array_diff($sourceTables, $targetTables); + foreach ($missingTables as $missingTable) { + $targetWriter->createTable($missingTable); + } + //First run, create tables and columns + foreach ($sourceTables as $sourceTable) { + $sourceColumns = $sourceWriter->getColumns($sourceTable); + if (in_array($sourceTable, $missingTables)) { + $targetColumns = array(); + } else { + $targetColumns = $targetWriter->getColumns($sourceTable); + } + unset($sourceColumns['id']); + foreach ($sourceColumns as $sourceColumn => $sourceType) { + if (substr($sourceColumn, -3) === '_id') { + $targetCode = $targetWriter->getTypeForID(); + } else { + $sourceCode = $sourceWriter->code($sourceType, true); + $targetCode = (isset($translations[$sourceCode])) ? $translations[$sourceCode] : $defaultCode; + } + if (!isset($targetColumns[$sourceColumn])) { + $targetWriter->addColumn($sourceTable, $sourceColumn, $targetCode); + } + } + } + + foreach ($sourceTables as $sourceTable) { + $sourceColumns = $sourceWriter->getColumns($sourceTable); + foreach ($sourceColumns as $sourceColumn => $sourceType) { + if (substr($sourceColumn, -3) === '_id') { + $fkTargetType = substr($sourceColumn, 0, strlen($sourceColumn) - 3); + $fkType = $sourceTable; + $fkField = $sourceColumn; + $fkTargetField = 'id'; + $targetWriter->addFK($fkType, $fkTargetType, $fkField, $fkTargetField); + } + } + //Is it a link table? -- Add Unique constraint and FK constraint + if (strpos($sourceTable, '_') !== false) { + $targetWriter->addUniqueIndex($sourceTable, array_keys($sourceColumns)); + $types = explode('_', $sourceTable); + $targetWriter->addConstraint(R::dispense($types[0]), R::dispense($types[1])); + } + } + } + + + /** + * Performs a database schema sync. For use with facade. + * Instead of toolboxes this method accepts simply string keys and is static. + * + * @param string $database1 the source database + * @param string $database2 the target database + */ + public static function syncSchema($database1,$database2) { + if (!isset(RedBean_Facade::$toolboxes[$database1])) throw new RedBean_Exception_Security('No database for this key: '.$database1); + if (!isset(RedBean_Facade::$toolboxes[$database2])) throw new RedBean_Exception_Security('No database for this key: '.$database2); + $db1 = RedBean_Facade::$toolboxes[$database1]; + $db2 = RedBean_Facade::$toolboxes[$database2]; + $sync = new self; + $sync->doSync($db1, $db2); + } + +} + + +/** + * BeanCan + * + * @file RedBean/BeanCan.php + * @desc Server Interface for RedBean and Fuse. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * The BeanCan Server is a lightweight, minimalistic server interface for + * RedBean that can perfectly act as an ORM middleware solution or a backend + * for an AJAX application. + * + * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_Plugin_BeanCan implements RedBean_Plugin { + + /** + * Holds a FUSE instance. + * @var RedBean_ModelHelper + */ + private $modelHelper; + + /** + * Constructor. + */ + public function __construct() { + $this->modelHelper = new RedBean_ModelHelper; + } + + /** + * Writes a response object for the client (JSON encoded). Internal method. + * + * @param mixed $result result + * @param integer $id request ID + * @param integer $errorCode error code from server + * @param string $errorMessage error message from server + * + * @return string $json JSON encoded response. + */ + private function resp($result=null, $id=null, $errorCode='-32603',$errorMessage='Internal Error') { + $response = array('jsonrpc'=>'2.0'); + if (!is_null($id)) { $response['id'] = $id; } + if ($result) { + $response['result']=$result; + } + else { + $response['error'] = array('code'=>$errorCode,'message'=>$errorMessage); + } + return (json_encode($response)); + } + + /** + * Processes a JSON object request. + * + * @param array $jsonObject JSON request object + * + * @return mixed $result result + */ + public function handleJSONRequest( $jsonString ) { + //Decode JSON string + $jsonArray = json_decode($jsonString,true); + if (!$jsonArray) return $this->resp(null,null,-32700,'Cannot Parse JSON'); + if (!isset($jsonArray['jsonrpc'])) return $this->resp(null,null,-32600,'No RPC version'); + if (($jsonArray['jsonrpc']!='2.0')) return $this->resp(null,null,-32600,'Incompatible RPC Version'); + //DO we have an ID to identify this request? + if (!isset($jsonArray['id'])) return $this->resp(null,null,-32600,'No ID'); + //Fetch the request Identification String. + $id = $jsonArray['id']; + //Do we have a method? + if (!isset($jsonArray['method'])) return $this->resp(null,$id,-32600,'No method'); + //Do we have params? + if (!isset($jsonArray['params'])) { + $data = array(); + } + else { + $data = $jsonArray['params']; + } + //Check method signature + $method = explode(':',trim($jsonArray['method'])); + if (count($method)!=2) { + return $this->resp(null, $id, -32600,'Invalid method signature. Use: BEAN:ACTION'); + } + //Collect Bean and Action + $beanType = $method[0]; + $action = $method[1]; + //May not contain anything other than ALPHA NUMERIC chars and _ + if (preg_match('/\W/',$beanType)) return $this->resp(null, $id, -32600,'Invalid Bean Type String'); + if (preg_match('/\W/',$action)) return $this->resp(null, $id, -32600,'Invalid Action String'); + + try { + switch($action) { + case 'store': + if (!isset($data[0])) return $this->resp(null, $id, -32602,'First param needs to be Bean Object'); + $data = $data[0]; + if (!isset($data['id'])) $bean = RedBean_Facade::dispense($beanType); else + $bean = RedBean_Facade::load($beanType,$data['id']); + $bean->import( $data ); + $rid = RedBean_Facade::store($bean); + return $this->resp($rid, $id); + case 'load': + if (!isset($data[0])) return $this->resp(null, $id, -32602,'First param needs to be Bean ID'); + $bean = RedBean_Facade::load($beanType,$data[0]); + return $this->resp($bean->export(),$id); + case 'trash': + if (!isset($data[0])) return $this->resp(null, $id, -32602,'First param needs to be Bean ID'); + $bean = RedBean_Facade::load($beanType,$data[0]); + RedBean_Facade::trash($bean); + return $this->resp('OK',$id); + case 'export': + if (!isset($data[0])) return $this->resp(null, $id, -32602,'First param needs to be Bean ID'); + $bean = RedBean_Facade::load($beanType,$data[0]); + $array = RedBean_Facade::exportAll(array($bean),true); + return $this->resp($array,$id); + default: + $modelName = $this->modelHelper->getModelName( $beanType ); + if (!class_exists($modelName)) return $this->resp(null, $id, -32601,'No such bean in the can!'); + $beanModel = new $modelName; + if (!method_exists($beanModel,$action)) return $this->resp(null, $id, -32601,"Method not found in Bean: $beanType "); + return $this->resp( call_user_func_array(array($beanModel,$action), $data), $id); + } + } + catch(Exception $exception) { + return $this->resp(null, $id, -32099,$exception->getCode().'-'.$exception->getMessage()); + } + } + + /** + * Support for RESTFul GET-requests. + * Only supports very BASIC REST requests, for more functionality please use + * the JSON-RPC 2 interface. + * + * @param string $pathToResource RESTFul path to resource + * + * @return string $json a JSON encoded response ready for sending to client + */ + public function handleRESTGetRequest( $pathToResource ) { + if (!is_string($pathToResource)) return $this->resp(null,0,-32099,'IR'); + $resourceInfo = explode('/',$pathToResource); + $type = $resourceInfo[0]; + try { + if (count($resourceInfo) < 2) { + return $this->resp(RedBean_Facade::findAndExport($type)); + } + else { + $id = (int) $resourceInfo[1]; + return $this->resp(RedBean_Facade::load($type,$id)->export(),$id); + } + } + catch(Exception $e) { + return $this->resp(null,0,-32099); + } + } +} + + + +/** + * Query Logger + * + * @file RedBean/Plugin/QueryLogger.php + * @desc Query logger, can be attached to an observer that signals the sql_exec event. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ + +class RedBean_Plugin_QueryLogger implements RedBean_Observer, RedBean_Plugin { + + /** + * @var array + * contains log messages + */ + protected $logs = array(); + + /** + * Creates a new instance of the Query Logger and attaches + * this logger to the adapter. + * + * @static + * @param RedBean_Observable $adapter the adapter you want to attach to + * + * @return RedBean_Plugin_QueryLogger $querylogger instance of the Query Logger + */ + public static function getInstanceAndAttach( RedBean_Observable $adapter ) { + $queryLog = new RedBean_Plugin_QueryLogger; + $adapter->addEventListener( 'sql_exec', $queryLog ); + return $queryLog; + } + + /** + * Singleton pattern + * Constructor - private + */ + private function __construct(){} + + /** + * Implementation of the onEvent() method for Observer interface. + * If a query gets executed this method gets invoked because the + * adapter will send a signal to the attached logger. + * + * @param string $eventName ID of the event (name) + * @param RedBean_DBAdapter $adapter adapter that sends the signal + * + * @return void + */ + public function onEvent( $eventName, $adapter ) { + if ($eventName=='sql_exec') { + $this->logs[] = $adapter->getSQL(); + } + } + + /** + * Searches the logs for the given word and returns the entries found in + * the log container. + * + * @param string $word word to look for + * + * @return array $entries entries that contain the keyword + */ + public function grep( $word ) { + $found = array(); + foreach($this->logs as $log) { + if (strpos($log,$word)!==false) { + $found[] = $log; + } + } + return $found; + } + + /** + * Returns all the logs. + * + * @return array $logs logs + */ + public function getLogs() { + return $this->logs; + } + + /** + * Clears the logs. + * + * @return void + */ + public function clear() { + $this->logs = array(); + } +} + + +/** + * TimeLine + * + * @file RedBean/Plugin/TimeLine.php + * @desc Monitors schema changes to ease deployment. + * + * @plugin public static function log($filename) { $tl = new RedBean_Plugin_TimeLine($filename); self::$adapter->addEventListener('sql_exec',$tl);} + * + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ + +class RedBean_Plugin_TimeLine extends RedBean_Plugin_QueryLogger implements RedBean_Plugin { + + /** + * Path to file to write SQL and comments to. + * + * @var string + */ + protected $file; + + /** + * Constructor. + * Requires a path to an existing and writable file. + * + * @param string $outputPath path to file to write schema changes to. + */ + public function __construct($outputPath) { + if (!file_exists($outputPath) || !is_writable($outputPath)) + throw new RedBean_Exception_Security('Cannot write to file: '.$outputPath); + $this->file = $outputPath; + } + + /** + * Implementation of the onEvent() method for Observer interface. + * If a query gets executed this method gets invoked because the + * adapter will send a signal to the attached logger. + * + * @param string $eventName ID of the event (name) + * @param RedBean_DBAdapter $adapter adapter that sends the signal + * + * @return void + */ + public function onEvent( $eventName, $adapter ) { + if ($eventName=='sql_exec') { + $sql = $adapter->getSQL(); + $this->logs[] = $sql; + if (strpos($sql,'ALTER')===0) { + $write = "-- ".date('Y-m-d H:i')." | Altering table. \n"; + $write .= $sql; + $write .= "\n\n"; + } + if (strpos($sql,'CREATE')===0) { + $write = "-- ".date('Y-m-d H:i')." | Creating new table. \n"; + $write .= $sql; + $write .= "\n\n"; + } + if (isset($write)) { + file_put_contents($this->file,$write,FILE_APPEND); + } + } + } + + +} + +/** + * RedBean Cooker + * + * @file RedBean/Cooker.php + * + * @plugin public static function graph($array,$filterEmpty=false) { $c = new RedBean_Plugin_Cooker(); $c->setToolbox(self::$toolbox);return $c->graph($array,$filterEmpty);} + * + * @desc Turns arrays into bean collections for easy persistence. + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * The Cooker is a little candy to make it easier to read-in an HTML form. + * This class turns a form into a collection of beans plus an array + * describing the desired associations. + * + * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community. + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ +class RedBean_Plugin_Cooker implements RedBean_Plugin { + + /** + * This flag indicates whether empty strings in beans will be + * interpreted as NULL or not. TRUE means Yes, will be converted to NULL, + * FALSE means empty strings will be stored as such (conversion to 0 for integer fields). + * @var boolean + */ + private static $useNULLForEmptyString = false; + + /** + * Sets the toolbox to be used by graph() + * + * @param RedBean_Toolbox $toolbox toolbox + * @return void + */ + public function setToolbox(RedBean_Toolbox $toolbox) { + $this->toolbox = $toolbox; + $this->redbean = $this->toolbox->getRedbean(); + } + + /** + * Turns an array (post/request array) into a collection of beans. + * Handy for turning forms into bean structures that can be stored with a + * single call. + * + * Typical usage: + * + * $struct = R::graph($_POST); + * R::store($struct); + * + * Example of a valid array: + * + * $form = array( + * 'type'=>'order', + * 'ownProduct'=>array( + * array('id'=>171,'type'=>'product'), + * ), + * 'ownCustomer'=>array( + * array('type'=>'customer','name'=>'Bill') + * ), + * 'sharedCoupon'=>array( + * array('type'=>'coupon','name'=>'123'), + * array('type'=>'coupon','id'=>3) + * ) + * ); + * + * Each entry in the array will become a property of the bean. + * The array needs to have a type-field indicating the type of bean it is + * going to be. The array can have nested arrays. A nested array has to be + * named conform the bean-relation conventions, i.e. ownPage/sharedPage + * each entry in the nested array represents another bean. + * + * @param array $array array to be turned into a bean collection + * @param boolean $filterEmpty whether you want to exclude empty beans + * + * @return array $beans beans + */ + public function graph( $array, $filterEmpty = false ) { + $beans = array(); + if (is_array($array) && isset($array['type'])) { + $type = $array['type']; + unset($array['type']); + //Do we need to load the bean? + if (isset($array['id'])) { + $id = (int) $array['id']; + $bean = $this->redbean->load($type,$id); + } + else { + $bean = $this->redbean->dispense($type); + } + foreach($array as $property=>$value) { + if (is_array($value)) { + $bean->$property = $this->graph($value,$filterEmpty); + } + else { + if($value == '' && self::$useNULLForEmptyString){ + $bean->$property = null; + } + else + $bean->$property = $value; + } + } + return $bean; + } + elseif (is_array($array)) { + foreach($array as $key=>$value) { + $listBean = $this->graph($value,$filterEmpty); + if (!($listBean instanceof RedBean_OODBBean)) { + throw new RedBean_Exception_Security('Expected bean but got :'.gettype($listBean)); + } + if ($listBean->isEmpty()) { + if (!$filterEmpty) { + $beans[$key] = $listBean; + } + } + else { + $beans[$key] = $listBean; + } + } + return $beans; + } + else { + throw new RedBean_Exception_Security('Expected array but got :'.gettype($array)); + } + } + + /** + * Toggles the use-NULL flag. + * + * @param boolean $yesNo + */ + public function setUseNullFlag($yesNo) { + self::$useNULLForEmptyString = (boolean) $yesNo; + } + +} + + +/** + * RedBeanPHP Cache Plugin + * + * @file RedBean/Plugin/Cache.php + * @desc Cache plugin, caches beans. + * @author Gabor de Mooij and the RedBeanPHP community + * @license BSD/GPLv2 + * + * Provides a means to cache beans after loading or batch loading. + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + */ + +class RedBean_Plugin_Cache extends RedBean_OODB implements RedBean_Plugin { + + /** + * Bean cache, contains the cached beans identified by + * label keys containing the type id and the fetch method; + * i.e. single load or batch load. + * + * @var array + */ + protected $cache = array(); + + + + /** + * Number of hits (beans/calls being served from cache). + * Can be used to monitor cache performance. + * + * @var integer + */ + protected $hits = 0; + + /** + * Number of misses (beans not being served from cache), can be + * used to monitor cache performance. + * + * @var integer + */ + protected $misses = 0; + + /** + * Constructor. + * Cache decorates RedBeanPHP OODB class, so needs a writer. + * + * @param RedBean_QueryWriter $writer + */ + public function __construct(RedBean_QueryWriter $writer) { + parent::__construct($writer); + } + + + + /** + * Loads a bean by type and id. If the bean cannot be found an + * empty bean will be returned instead. This is a cached version + * of the loader, if the bean has been cached it will be served + * from cache, otherwise the bean will be retrieved from the database + * as usual an a new cache entry will be added.. + * + * @param string $type type of bean you are looking for + * @param integer $id identifier of the bean + * + * @return RedBean_OODBBean $bean the bean object found + */ + public function load($type,$id) { + if (isset($this->cache[$type][$id])) { + $this->hits ++; + $bean = $this->cache[$type][$id]; + } + else { + $this->misses ++; + $bean = parent::load($type,$id); + if ($bean->id) { + if (!isset($this->cache[$type])) $this->cache[$type]=array(); + $this->cache[$type][$id] = $bean; + } + } + return $bean; + } + + /** + * Stores a RedBean OODBBean and caches it. + * + * @param RedBean_OODBBean $bean the bean you want to store + * + * @return integer $id + */ + public function store( $bean ) { + $id = parent::store($bean); + $type = $bean->getMeta('type'); + if (!isset($this->cache[$type])) $this->cache[$type]=array(); + $this->cache[$type][$id] = $bean; + return $id; + } + + /** + * Trashes a RedBean OODBBean and removes it from cache. + * + * @param RedBean_OODBBean $bean bean + * @return mixed + */ + public function trash( $bean ) { + $type = $bean->getMeta('type'); + $id = $bean->id; + if (isset($this->cache[$type][$id])) unset($this->cache[$type][$id]); + return parent::trash($bean); + } + + /** + * Flushes the cache for a given type. + * + * @param string $type + * + * @return RedBean_Plugin_Cache + */ + public function flush($type) { + if (isset($this->cache[$type])) $this->cache[$type]=array(); + return $this; + } + + /** + * Flushes the cache completely. + * + * @return RedBean_Plugin_Cache + */ + public function flushAll() { + $this->cache = array(); + return $this; + } + + + /** + * Returns the number of hits. If a call to load() or + * batch() can use the cache this counts as a hit. + * Otherwise it's a miss. + * + * @return integer + */ + public function getHits() { + return $this->hits; + } + + /** + * Returns the number of hits. If a call to load() or + * batch() can use the cache this counts as a hit. + * Otherwise it's a miss. + * + * @return integer + */ + public function getMisses() { + return $this->misses; + } + + /** + * Resets hits counter to 0. + */ + public function resetHits() { + $this->hits = 0; + } + + /** + * Resets misses counter to 0. + */ + public function resetMisses() { + $this->misses = 0; + } + +} + +/** + * RedBean Dependency Injector + * + * @file RedBean/DependencyInjector.php + * @desc Simple dependency injector + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * A default dependency injector that can be subclassed to + * suit your needs. This injetor can be used to inject helper objects into + * FUSE(d) models. + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + * + */ +class RedBean_DependencyInjector { + + /** + * List of dependencies. + * @var array + */ + protected $dependencies = array(); + + /** + * Adds a dependency to the list. + * You can add dependencies using this method. Pass both the key of the + * dependency and the dependency itself. The key of the dependency is a + * name that should match the setter. For instance if you have a dependency + * class called My_Mailer and a setter on the model called setMailSystem + * you should pass an instance of My_Mailer with key MailSystem. + * The injector will now look for a setter called setMailSystem. + * + * @param string $dependencyID name of the dependency (should match setter) + * @param mixed $dependency the service to be injected + */ + public function addDependency($dependencyID,$dependency) { + $this->dependencies[$dependencyID] = $dependency; + } + + /** + * Returns an instance of the class $modelClassName completely + * configured as far as possible with all the available + * service objects in the dependency list. + * + * @param string $modelClassName the name of the class of the model + * + * @return mixed $object the model/object + */ + public function getInstance($modelClassName) { + $object = new $modelClassName; + if ($this->dependencies && is_array($this->dependencies)) { + foreach($this->dependencies as $key=>$dep) { + $depSetter = 'set'.$key; + if (method_exists($object,$depSetter)) { + $object->$depSetter($dep); + } + } + } + return $object; + } +} + +/** + * RedBean Duplication Manager + * + * @file RedBean/DuplicationManager.php + * @desc Creates deep copies of beans + * @author Gabor de Mooij and the RedBeanPHP Community + * @license BSD/GPLv2 + * + * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community + * This source file is subject to the BSD/GPLv2 License that is bundled + * with this source code in the file license.txt. + * + */ +class RedBean_DuplicationManager { + + + /** + * The Dup Manager requires a toolbox + * @var RedBean_Toolbox + */ + protected $toolbox; + + /** + * Association Manager + * @var RedBean_AssociationManager + */ + protected $associationManager; + + /** + * RedBeanPHP OODB instance + * @var RedBean_OODBBean + */ + protected $redbean; + + protected $tables = array(); + protected $columns = array(); + protected $filters = array(); + protected $cacheTables = false; + /** + * Constructor, + * creates a new instance of DupManager. + * @param RedBean_Toolbox $toolbox + */ + public function __construct( RedBean_Toolbox $toolbox ) { + $this->toolbox = $toolbox; + $this->redbean = $toolbox->getRedBean(); + $this->associationManager = $this->redbean->getAssociationManager(); + } + + /** + * For better performance you can pass the tables in an array to this method. + * If the tables are available the duplication manager will not query them so + * this might be beneficial for performance. + * + * @param array $tables + */ + public function setTables($tables) { + foreach($tables as $key=>$value) { + if (is_numeric($key)) { + $this->tables[] = $value; + } + else { + $this->tables[] = $key; + $this->columns[$key] = $value; + } + } + $this->cacheTables = true; + } + + /** + * Returns a schema array for cache. + * + * @return array + */ + public function getSchema() { + return $this->columns; + } + + /** + * Indicates whether you want the duplication manager to cache the database schema. + * If this flag is set to TRUE the duplication manager will query the database schema + * only once. Otherwise the duplicationmanager will, by default, query the schema + * every time a duplication action is performed (dup()). + * + * @param boolean $yesNo + */ + public function setCacheTables($yesNo) { + $this->cacheTables = $yesNo; + } + + /** + * A filter array is an array with table names. + * By setting a table filter you can make the duplication manager only take into account + * certain bean types. Other bean types will be ignored when exporting or making a + * deep copy. If no filters are set all types will be taking into account, this is + * the default behavior. + * + * @param array $filters + */ + public function setFilters($filters) { + $this->filters = $filters; + } + + /** + * Determines whether the bean has an own list based on + * schema inspection from realtime schema or cache. + * + * @param string $type bean type + * @param string $target type of list you want to detect + * + * @return boolean + */ + protected function hasOwnList($type,$target) { + return (isset($this->columns[$target][$type.'_id'])); + } + + /** + * Determines whether the bea has a shared list based on + * schema inspection from realtime schema or cache. + * + * @param string $type bean type + * @param string $target type of list you are looking for + * + * @return boolean + */ + protected function hasSharedList($type,$target) { + $linkType = array($type,$target); + sort($linkType); + $linkType = implode('_',$linkType); + return (in_array($linkType,$this->tables)); + } + + /** + * Makes a copy of a bean. This method makes a deep copy + * of the bean.The copy will have the following features. + * - All beans in own-lists will be duplicated as well + * - All references to shared beans will be copied but not the shared beans themselves + * - All references to parent objects (_id fields) will be copied but not the parents themselves + * In most cases this is the desired scenario for copying beans. + * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found + * (i.e. one that already has been processed) the ID of the bean will be returned. + * This should not happen though. + * + * Note: + * This function does a reflectional database query so it may be slow. + * + * Note: + * this function actually passes the arguments to a protected function called + * duplicate() that does all the work. This method takes care of creating a clone + * of the bean to avoid the bean getting tainted (triggering saving when storing it). + * + * @param RedBean_OODBBean $bean bean to be copied + * @param array $trail for internal usage, pass array() + * @param boolean $pid for internal usage + * + * @return array $copiedBean the duplicated bean + */ + public function dup($bean,$trail=array(),$pid=false) { + if (!count($this->tables)) $this->tables = $this->toolbox->getWriter()->getTables(); + if (!count($this->columns)) foreach($this->tables as $table) $this->columns[$table] = $this->toolbox->getWriter()->getColumns($table); + $beanCopy = clone($bean); + $rs = $this->duplicate($beanCopy,$trail,$pid); + if (!$this->cacheTables) { + $this->tables = array(); + $this->columns = array(); + } + return $rs; + } + + /** + * Makes a copy of a bean. This method makes a deep copy + * of the bean.The copy will have the following features. + * - All beans in own-lists will be duplicated as well + * - All references to shared beans will be copied but not the shared beans themselves + * - All references to parent objects (_id fields) will be copied but not the parents themselves + * In most cases this is the desired scenario for copying beans. + * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found + * (i.e. one that already has been processed) the ID of the bean will be returned. + * This should not happen though. + * + * Note: + * This function does a reflectional database query so it may be slow. + * + * @param RedBean_OODBBean $bean bean to be copied + * @param array $trail for internal usage, pass array() + * @param boolean $pid for internal usage + * + * @return array $copiedBean the duplicated bean + */ + protected function duplicate($bean,$trail=array(),$pid=false) { + + $type = $bean->getMeta('type'); + $key = $type.$bean->getID(); + if (isset($trail[$key])) return $bean; + $trail[$key]=$bean; + $copy =$this->redbean->dispense($type); + $copy->import( $bean->getProperties() ); + $copy->id = 0; + $tables = $this->tables; + foreach($tables as $table) { + if (is_array($this->filters) && count($this->filters) && !in_array($table,$this->filters)) continue; + if (strpos($table,'_')!==false || $table==$type) continue; + $owned = 'own'.ucfirst($table); + $shared = 'shared'.ucfirst($table); + if ($this->hasOwnList($type,$table)) { + if ($beans = $bean->$owned) { + $copy->$owned = array(); + foreach($beans as $subBean) { + array_push($copy->$owned,$this->duplicate($subBean,$trail,$pid)); + } + } + $copy->setMeta('sys.shadow.'.$owned,null); + } + if ($this->hasSharedList($type, $table)) { + if ($beans = $bean->$shared) { + $copy->$shared = array(); + foreach($beans as $subBean) { + array_push($copy->$shared,$subBean); + } + } + } + $copy->setMeta('sys.shadow.'.$shared,null); + + } + if ($pid) $copy->id = $bean->id; + return $copy; + } +} + +class R extends RedBean_Facade{ + public static function syncSchema($from,$to) { return RedBean_Plugin_Sync::syncSchema($from,$to); } + public static function log($filename) { $tl = new RedBean_Plugin_TimeLine($filename); self::$adapter->addEventListener('sql_exec',$tl);} + public static function graph($array,$filterEmpty=false) { $c = new RedBean_Plugin_Cooker(); $c->setToolbox(self::$toolbox);return $c->graph($array,$filterEmpty);} +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/list.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/list.php new file mode 100644 index 0000000..eb52b2a --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/list.php @@ -0,0 +1,144 @@ + + + + + PhotoAlbum - Main page + + + + "fetch")); ?>"/> + + + + + + + + + +
+ + "png", + "transformation" => array( + array( + "height" => 95, + "width" => 95, + "crop" => "thumb", + "gravity" => "face", + "effect" => "sepia", + "radius" => 20, + ), + array("angle" => 10), + ), + )); + ?> +
+ +

Welcome!

+ +

+ This is the main demo page of the PhotoAlbum sample PHP application of Cloudinary.
+ Here you can see all images you have uploaded to this PHP application and find some information on how + to implement your own PHP application storing, manipulating and serving your photos using Cloudinary! +

+ +

+ All of the images you see here are transformed and served by Cloudinary. + For instance, the logo and the poster frame. + They are both generated in the cloud using the Cloudinary shortcut functions: fetch_image_tag and + facebook_profile_image_tag. + These two pictures weren't even have to be uploaded to Cloudinary, they are retrieved by the service, transformed, + cached and distributed through a CDN. +

+ +

Your Images

+
+

+ Following are the images uploaded by you. You can also upload more pictures. + + You can click on each picture to view its original size, and see more info about and additional transformations. + Upload Images... +

+ +

No images were uploaded yet.

+ + "; + echo cl_image_tag($photo["public_id"], array_merge($thumbs_params, array("crop" => "fill"))); + ?> + + + + +
+ Hide transformations... + + "fill", "radius" => 10), + array("crop" => "scale"), + array("crop" => "fit", "format" => "png"), + array("crop" => "thumb", "gravity" => "face"), + array( + "override" => true, + "format" => "png", + "angle" => 20, + "transformation" => + array("crop" => "fill", + "gravity" => "north", + "width" => 150, + "height" => 150, + "effect" => "sepia", + ), + ), + ); + foreach ($thumbs as $params) { + $merged_params = array_merge((\Cloudinary::option_consume($params, "override")) + ? array() : $thumbs_params, $params); + echo ""; + } + ?> + +
"; + echo ""; + echo "
"; + \PhotoAlbum\array_to_table($merged_params); + echo "
+ +
+ Take a look at our documentation of Image + Transformations for a full list of supported transformations. +
+
+
+ + + + diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/main.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/main.php new file mode 100644 index 0000000..30de1d4 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/main.php @@ -0,0 +1,64 @@ + 'jpg', 'height' => 150, 'width' => 150, 'class' => 'thumbnail inline'); + +// Helper functions +function ret_var_dump($var) +{ + ob_start(); + var_dump($var); + return ob_get_clean(); +} + +function array_to_table($array) +{ + $saved_error_reporting = error_reporting(0); + echo ''; + foreach ($array as $key => $value) { + if ($key != 'class') { + if ($key == 'url' || $key == 'secure_url') { + $display_value = '"' . $value . '"'; + } else { + $display_value = json_encode($value); + } + echo ""; + } + } + echo '
{$key}:{$display_value}
'; + error_reporting($saved_error_reporting); +} + +function create_photo_model($options = array()) +{ + $photo = \R::dispense('photo'); + + foreach ($options as $key => $value) { + if ($key != 'tags') { + $photo->{$key} = $value; + } + } + + # Add metadata we want to keep: + $photo->moderated = false; + $photo->created_at = (array_key_exists('created_at', $photo) ? + \DateTime::createFromFormat(\DateTime::ISO8601, $photo->created_at) : + \R::isoDateTime()); + + \R::store($photo); +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/settings.php.sample b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/settings.php.sample new file mode 100644 index 0000000..5edf3e1 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/settings.php.sample @@ -0,0 +1,8 @@ + 'my_cloud_name', + 'api_key' => '123456789012345', + 'api_secret' => 'abcdefghijklmnopqrstuvwxyz1' +)); + +R::setup('mysql:host=my_database.mydomain.com;dbname=photo_album', 'username', 'password'); diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/style.css b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/style.css new file mode 100644 index 0000000..77f9bb9 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/style.css @@ -0,0 +1,37 @@ +body { font-family: Helvetica, Arial, sans-serif; color: #333; margin: 10px; width: 960px } +#posterframe { position: absolute; right: 10px; top: 10px; } +h1 { color: #0e2953; font-size: 18px; } +h2 { color: #666; font-size: 16px; } +p { font-size: 14px; line-height: 18px; } +#logo { height: 51px; width: 241px; } +a { color: #0b63b6 } + +.upload_link { display: block; color: #000; border: 1px solid #aaa; background-color: #e0e0e0; + font-size: 18px; padding: 5px 10px; width: 150px; margin: 10px 0 20px 0; + font-weight: bold; text-align: center; } + +.photo { margin: 10px; padding: 10px; border-bottom: 2px solid #ccc; } +.photo .thumbnail { margin-top: 10px; display: block; max-width: 200px; } +.toggle_info { margin-top: 10px; font-weight: bold; color: #e62401; display: block; } +.thumbnail_holder { height: 182px; margin-bottom: 5px; margin-right: 10px; } +.info td, .uploaded_info td { font-size: 12px } +.note { margin: 20px 0} + +.more_info, .show_more_info .less_info { display: none; } +.show_more_info .more_info, .less_info { display: inline-block; } +.inline { display: inline-block; } +td { vertical-align: top; padding-right: 5px; } + +#backend_upload, #direct_upload { padding: 20px 20px; margin: 20px 0; + border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } + +#backend_upload h1, #direct_upload h1 { margin: 0 0 15px 0; } + +.back_link { font-weight: bold; display: block; font-size: 16px; margin: 10px 0; } +#direct_upload { position: relative; min-height: 90px} +.status { position: absolute; top: 20px; left: 500px; text-align: center; border: 1px solid #aaa; + padding: 10px; width: 200px } + +.uploaded_info { margin: 10px} +.uploaded_info .image { margin: 5px 0 } +.uploaded_info td { padding-right: 10px } diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/upload.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/upload.php new file mode 100644 index 0000000..9b38b5a --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/upload.php @@ -0,0 +1,154 @@ + + + + + + PhotoAlbum - Upload page2 + + + + "fetch")); ?>"/> + + + + + + + + + + + + + +
+ + "png", + "transformation" => array( + array( + "height" => 95, + "width" => 95, + "crop" => "thumb", + "gravity" => "face", + "effect" => "sepia", + "radius" => 20, + ), + array("angle" => 10), + ), + )); + ?> +
+ + +
+

Upload through your server

+
+ + +
+
+ + + + +
+

Direct upload from the browser

+
+ upload_preset($upload_preset); + } catch (\Cloudinary\Api\NotFound $e) { + $api->create_upload_preset(array( + "name" => $upload_preset, + "unsigned" => true, + "folder" => "preset_folder", + )); + } + # The callback URL is set to point to an HTML file on the local server which works-around restrictions + # in older browsers (e.g., IE) which don't full support CORS. + echo cl_unsigned_image_upload_tag('test', $upload_preset, array( + "tags" => "direct_photo_album", + "callback" => $cors_location, + "html" => array("multiple" => true), + )); + } else { + # The callback URL is set to point to an HTML file on the local server which works-around restrictions + # in older browsers (e.g., IE) which don't full support CORS. + echo cl_image_upload_tag('test', array( + "tags" => "direct_photo_album", + "callback" => $cors_location, + "html" => array("multiple" => true), + )); + } + ?> + +
+ +
+

Status

+ Idle +
+ +
+
+
+ +Back to list... + + + + diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/upload_backend.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/upload_backend.php new file mode 100644 index 0000000..73cc230 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/upload_backend.php @@ -0,0 +1,46 @@ + "backend_photo_album", + "public_id" => $orig_name, + )); + + unlink($file_path); + error_log("Upload result: " . \PhotoAlbum\ret_var_dump($result)); + \PhotoAlbum\create_photo_model($result); + return $result; +} + +$files = $_FILES["files"]; +$files = is_array($files) ? $files : array( $files ); +$files_data = array(); +foreach ($files["tmp_name"] as $index => $value) { + array_push($files_data, create_photo($value, $files["name"][$index])); +} + +?> + + + + Upload succeeded! + + + +

Your photo has been uploaded sucessfully!

+

Upload details:

+ +
+ "fill" ))); ?> + +Back to list... + + + diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/upload_complete.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/upload_complete.php new file mode 100644 index 0000000..5e38da6 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/PhotoAlbum/upload_complete.php @@ -0,0 +1,27 @@ + $_POST['public_id'], + 'version' => $_POST['version'], +); + +$calculated_signature = \Cloudinary::api_sign_request($to_sign, $api_secret); + +if ($existing_signature == $calculated_signature) { + # Create a model record using the data received (best practice is to save locally + # only data needed for reaching the image on Cloudinary - public_id and version; + # and fields that might be needed for your application (e.g.,), width, height) + \PhotoAlbum\create_photo_model($_POST); +} else { + error_log("Received signature verification failed (" . + $existing_signature . " != " . $calculated_signature . "). data: " . + \PhotoAlbum\ret_var_dump($_POST)); +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/README.md b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/README.md new file mode 100644 index 0000000..0724893 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/README.md @@ -0,0 +1,63 @@ +# Cloudinary PHP Sample Project + +Included in this folder are two sample projects for demonstrating the common Cloudinary's usage in PHP. + +*Note: PHP 5.3+ is required.* + +## Installation + +The cloudinary_php package is ready to be used as-is in your Apache server. (other servers are also supported, but the access restrictions set in .htaccess might not work). +As described in cloudinary\_php main README.md file, you have to set up your Cloudinary credentials either by passing it as the `CLOUDINARY_URL` environment variable or calling Cloudinary::config(). +Each sample tries to include `settings.php` (`Config/CloudinaryPrivate.php` for PhotoAlbumCake) for configuration data - you can use the included `settings.php.sample` as a basis for such file. + +## Basic sample + +This sample is a synchronous script that shows the upload process from local file, remote URL, with different transformations and options. + +You can access it through http://YOUR\_SERVER/PATH\_TO\_CLOUDINARY\_PHP/samples/basic/basic.php + +Another option is available if you are using PHP 5.4 or higher. + +First install the library: + +```bash +php composer.phar dump-autoload --optimize +``` + +Then run the basic sample: + +```bash +php -S localhost:8001 -t samples/basic +``` + +Then you can simply browse to: [http://localhost:8001/basic.php](http://localhost:8001/basic.php) + +## Photo Album + +A simple web application that allows you to uploads photos, maintain a database with references to them, list them with their metadata, and display them using various cloud-based transformations. + +Make sure to first create a MySQL database (e.g., `create database photo_album`). Then edit `settings.php` to have the correct database details. For example: + + R::setup('mysql:host=127.0.0.1;dbname=photo_album', 'my_db_user', 'my_db_password'); + +You can access it through http://YOUR\_SERVER/PATH\_TO\_CLOUDINARY\_PHP/samples/PhotoAlbum/list.php + +Another option is available if you are using PHP 5.4 or higher. + +First install the library: + +```bash +php composer.phar dump-autoload --optimize +``` + +Then run the basic sample: + +```bash +php -S localhost:8001 -t samples/PhotoAlbum +``` + +Then you can simply browse to: [http://localhost:8001/list.php](http://localhost:8001/list.php) + +## Photo Album Cake + +See the [Cloudinary CakePHP project](https://github.com/cloudinary/cloudinary_cake_php/tree/master/samples). diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/basic/.gitignore b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/basic/.gitignore new file mode 100644 index 0000000..edd8de6 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/basic/.gitignore @@ -0,0 +1 @@ +settings.php diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/basic/basic.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/basic/basic.php new file mode 100644 index 0000000..56fa683 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/basic/basic.php @@ -0,0 +1,196 @@ + getcwd() . DIRECTORY_SEPARATOR . 'pizza.jpg', + 'lake' => getcwd() . DIRECTORY_SEPARATOR . 'lake.jpg', + 'couple' => 'http://res.cloudinary.com/demo/image/upload/couple.jpg', +); + + +$default_upload_options = array('tags' => 'basic_sample'); +$eager_params = array('width' => 200, 'height' => 150, 'crop' => 'scale'); +$files = array(); + + +/** + * This function, when called uploads all files into your Cloudinary storage and saves the + * metadata to the $files array. + */ +function do_uploads() +{ + global $files, $sample_paths, $default_upload_options, $eager_params; + + # public_id will be generated on Cloudinary's backend. + $files['unnamed_local'] = \Cloudinary\Uploader::upload($sample_paths['pizza'], $default_upload_options); + + # Same image, uploaded with a public_id + $files['named_local'] = \Cloudinary\Uploader::upload( + $sample_paths['pizza'], + array_merge( + $default_upload_options, + array('public_id' => 'custom_name') + ) + ); + + # Eager transformations are applied as soon as the file is uploaded, instead of waiting + # for a user to request them. + $files['eager'] = \Cloudinary\Uploader::upload( + $sample_paths['lake'], + array_merge( + $default_upload_options, + array( + 'public_id' => 'eager_custom_name', + 'eager' => $eager_params, + ) + ) + ); + + # In the two following examples, the file is fetched from a remote URL and stored in Cloudinary. + # This allows you to apply the same transformations, and serve those using Cloudinary's CDN layer. + $files['remote'] = \Cloudinary\Uploader::upload( + $sample_paths['couple'], + $default_upload_options + ); + + $files['remote_trans'] = \Cloudinary\Uploader::upload( + $sample_paths['couple'], + array_merge( + $default_upload_options, + array( + 'width' => 500, + 'height' => 500, + 'crop' => 'fit', + 'effect' => 'saturation:-70', + ) + ) + ); +} + +/** + * Output an image in HTML along with provided caption and public_id + * + * @param $img + * @param array $options + * @param string $caption + */ +function show_image($img, $options = array(), $caption = '') +{ + $options['format'] = $img['format']; + $transformation_url = cloudinary_url($img['public_id'], $options); + + echo '
'; + echo '
' . $caption . '
'; + echo '' . cl_image_tag($img['public_id'], $options) . ''; + echo '
' . $img['public_id'] . '
'; + + echo ''; + echo '
'; +} + +?> + + + + Cloudinary - Basic PHP Sample + + + + +

Cloudinary - Basic PHP Sample

+

Uploading ...

+ +

... Done uploading!

+ + 200, 'height' => 150, 'crop' => 'fill'), + 'Local file, Fill 200x150' + ); + + show_image( + $files['named_local'], + array('width' => 200, 'height' => 150, 'crop' => 'fit'), + 'Local file, custom public ID, Fit into 200x150' + ); + + show_image( + $files['eager'], + $eager_params, + 'Local file, Eager trasnformation of scaling to 200x150' + ); + + show_image( + $files['remote'], + array('width' => 200, 'height' => 150, 'crop' => 'thumb', 'gravity' => 'faces'), + 'Uploaded remote image, Face detection based 200x150 thumbnail' + ); + + show_image( + $files['remote_trans'], + array('width' => 200, + 'height' => 150, + 'crop' => 'fill', + 'gravity' => 'face', + 'radius' => 10, + 'effect' => 'sepia' + ), + 'Uploaded remote image, Fill 200x150, round corners, apply the sepia effect' + ); +?> + + diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/basic/lake.jpg b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/basic/lake.jpg new file mode 100644 index 0000000..062a217 Binary files /dev/null and b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/basic/lake.jpg differ diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/basic/pizza.jpg b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/basic/pizza.jpg new file mode 100644 index 0000000..43353c6 Binary files /dev/null and b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/basic/pizza.jpg differ diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/basic/settings.php.sample b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/basic/settings.php.sample new file mode 100644 index 0000000..afb1fb9 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/samples/basic/settings.php.sample @@ -0,0 +1,7 @@ + 'my_cloud_name', + 'api_key' => 'my_api_key', + 'api_secret' => 'my_api_secret' +)); diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Api.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Api.php new file mode 100644 index 0000000..3e8f864 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Api.php @@ -0,0 +1,1589 @@ + "\Cloudinary\Api\BadRequest", + 401 => "\Cloudinary\Api\AuthorizationRequired", + 403 => "\Cloudinary\Api\NotAllowed", + 404 => "\Cloudinary\Api\NotFound", + 409 => "\Cloudinary\Api\AlreadyExists", + 420 => "\Cloudinary\Api\RateLimited", + 500 => "\Cloudinary\Api\GeneralError", + ); + + /** + * Tests the reachability of the Cloudinary API + * + * @see https://cloudinary.com/documentation/admin_api#ping_cloudinary + * + * @param array $options Additional options + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function ping($options = array()) + { + return $this->call_api("get", array("ping"), array(), $options); + } + + /** + * Gets account usage details + * + * Get a report on the status of your Cloudinary account usage details, including + * storage, bandwidth, requests, number of resources, and add-on usage. + * Note that numbers are updated periodically. + * + * @see https://cloudinary.com/documentation/admin_api#usage_report + * + * @param array $options Additional options + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function usage($options = array()) + { + return $this->call_api("get", array("usage"), array(), $options); + } + + /** + * Lists available resource types + * + * @param array $options Additional options + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function resource_types($options = array()) + { + return $this->call_api("get", array("resources"), array(), $options); + } + + /** + * Lists all uploaded resources optionally filtered by the specified options + * + * @see https://cloudinary.com/documentation/admin_api#browse_resources + * + * @param array $options { + * + * @var string resource_type The type of file. Default: image. + * Valid values: image, raw, video. + * @var string type The storage type. Default: all. + * Valid values: upload, private, authenticated, facebook, twitter, gplus, instagram_name, gravatar, + * youtube, hulu, vimeo, animoto, worldstarhiphop or dailymotion + * @var string prefix Find resources with a public ID that starts with the given prefix + * @var string|array public_ids List resources with the given public IDs (up to 100) + * @var int max_results Max number of resources to return. Default: 10. Maximum: 500 + * @var string next_cursor When a listing request has more results to return than max_results, + * the next_cursor value is returned as part of the response. You can then specify this value as + * the next_cursor parameter of the following listing request. + * @var string start_at List resources that were created since the given timestamp(ISO). + * Supported if no prefix or public IDs were specified. + * @var string|int direction Control the order of returned resources. + * Valid values: "asc" (or 1), "desc" (or -1). Default: "desc". + * Note that if a prefix is specified, this parameter is ignored + * and the results are sorted by public ID. + * @var boolean tags Include the list of tag names assigned each resource. Default: false + * @var boolean context Include key-value pairs of context associated with each resource. + * Default: false + * @var boolean moderations Include image moderation status of each listed resource. + * Default: false + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function resources($options = array()) + { + $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); + $type = \Cloudinary::option_get($options, "type"); + $uri = array("resources", $resource_type); + if ($type) { + array_push($uri, $type); + } + $params = $this->only( + $options, + array( + "next_cursor", + "max_results", + "prefix", + "tags", + "context", + "moderations", + "direction", + "start_at", + ) + ); + + return $this->call_api("get", $uri, $params, $options); + } + + /** + * Lists resources by tag + * + * Retrieve a list of resources with a specified tag. + * This method does not return deleted resources even if they have been backed up. + * + * @see https://cloudinary.com/documentation/admin_api#list_resources_by_tag + * + * @param string $tag The tag name of the resources + * @param array $options { + * + * @var string resource_type The type of file. Default: image. + * Valid values: image, raw, video. + * @var int max_results Max number of resources to return. Default: 10. Maximum: 500 + * @var string next_cursor When a listing request has more results to return than max_results, + * the next_cursor value is returned as part of the response. You can then specify this value as + * the next_cursor parameter of the following listing request. + * @var string|int direction Control the order of returned resources. + * Valid values: "asc" (or 1), "desc" (or -1). Default: "desc". + * Note that if a prefix is specified, this parameter is ignored + * and the results are sorted by public ID. + * @var boolean tags Include the list of tag names assigned each resource. Default: false + * @var boolean context Include key-value pairs of context associated with each resource. + * Default: false + * @var boolean moderations Include image moderation status of each listed resource. + * Default: false + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function resources_by_tag($tag, $options = array()) + { + $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); + $uri = array("resources", $resource_type, "tags", $tag); + $params = $this->only( + $options, + array("next_cursor", "max_results", "tags", "context", "moderations", "direction") + ); + + return $this->call_api("get", $uri, $params, $options); + } + + /** + * Lists resources by context + * + * Retrieve a list of resources with a specified context key. + * This method does not return deleted resources even if they have been backed up. + * + * @see https://cloudinary.com/documentation/admin_api#list_resources_by_context + * + * @param string $key Only resources with this context key are returned + * @param string $value Only resources with this value for the context key are returned. + * If this parameter is not provided, all resources with the given context key are returned, + * regardless of the actual value of the key. + * @param array $options { + * + * @var string resource_type The type of file. Default: image. + * Valid values: image, raw, video. + * @var int max_results Max number of resources to return. Default: 10. Maximum: 500 + * @var string next_cursor When a listing request has more results to return than max_results, + * the next_cursor value is returned as part of the response. You can then specify this value as + * the next_cursor parameter of the following listing request. + * @var string|int direction Control the order of returned resources. + * Valid values: "asc" (or 1), "desc" (or -1). Default: "desc". + * Note that if a prefix is specified, this parameter is ignored + * and the results are sorted by public ID. + * @var boolean tags Include the list of tag names assigned each resource. Default: false + * @var boolean context Include key-value pairs of context associated with each resource. + * Default: false + * @var boolean moderations Include image moderation status of each listed resource. + * Default: false + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function resources_by_context($key, $value = null, $options = array()) + { + $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); + $uri = array("resources", $resource_type, "context"); + $params = $this->only( + $options, + array("next_cursor", "max_results", "tags", "context", "moderations", "direction") + ); + $params["key"] = $key; + $params["value"] = $value; + + return $this->call_api("get", $uri, $params, $options); + } + + /** + * Lists resources in moderation queues + * + * @see https://cloudinary.com/documentation/admin_api#list_resources_in_moderation_queues + * + * @param string $kind Type of image moderation queue to list. + * Valid values: "manual", "webpurify", "aws_rek", or "metascan" + * @param string $status Moderation status of resources. + * Valid values: "pending", "approved", "rejected" + * @param array $options { + * + * @var string resource_type The type of file. Default: image. + * Valid values: image, raw, video. + * @var int max_results Max number of resources to return. Default: 10. Maximum: 500 + * @var string next_cursor When a listing request has more results to return than max_results, + * the next_cursor value is returned as part of the response. You can then specify this value as + * the next_cursor parameter of the following listing request. + * @var string|int direction Control the order of returned resources. + * Valid values: "asc" (or 1), "desc" (or -1). Default: "desc". + * Note that if a prefix is specified, this parameter is ignored + * and the results are sorted by public ID. + * @var boolean tags Include the list of tag names assigned each resource. Default: false + * @var boolean context Include key-value pairs of context associated with each resource. + * Default: false + * @var boolean moderations Include image moderation status of each listed resource. + * Default: false + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function resources_by_moderation($kind, $status, $options = array()) + { + $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); + $uri = array("resources", $resource_type, "moderations", $kind, $status); + $params = $this->only( + $options, + array("next_cursor", "max_results", "tags", "context", "moderations", "direction") + ); + + return $this->call_api("get", $uri, $params, $options); + } + + /** + * Lists resources by public IDs + * + * @see https://cloudinary.com/documentation/admin_api#list_all_uploaded_images_with_the_given_ids + * + * @param string|array public_ids List resources with the given public IDs (up to 100) + * @param array $options { + * + * @var string resource_type The type of file. Default: image. + * Valid values: image, raw, video. + * @var string type The storage type. Default: all. + * Valid values: upload, private, authenticated, facebook, twitter, gplus, instagram_name, gravatar, + * youtube, hulu, vimeo, animoto, worldstarhiphop or dailymotion + * @var boolean tags Include the list of tag names assigned each resource. Default: false + * @var boolean context Include key-value pairs of context associated with each resource. + * Default: false + * @var boolean moderations Include image moderation status of each listed resource. + * Default: false + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function resources_by_ids($public_ids, $options = array()) + { + $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); + $type = \Cloudinary::option_get($options, "type", "upload"); + $uri = array("resources", $resource_type, $type); + $params = array_merge($options, array("public_ids" => $public_ids)); + $params = $this->only($params, array("public_ids", "tags", "moderations", "context")); + + return $this->call_api("get", $uri, $params, $options); + } + + /** + * Details of a single resource + * + * Return details of the requested resource as well as all its derived resources. + * Note that if you only need details about the original resource, + * you can also use the Uploader::upload or Uploader::explicit methods, which are not rate limited. + * + * @see https://cloudinary.com/documentation/admin_api#details_of_a_single_resource + * + * @param string $public_id The public ID of the resource. + * @param array $options { + * + * @var string resource_type The type of file. Default: image. Valid values: image, raw, video. + * @var string type The storage type. Default: all. + * Valid values: upload, private, authenticated, facebook, twitter, gplus, instagram_name, gravatar, + * youtube, hulu, vimeo, animoto, worldstarhiphop or dailymotion + * @var boolean colors Include color information: predominant colors and histogram of 32 + * leading colors. Default: false + * @var boolean image_metadata Include IPTC, XMP, and detailed Exif metadata. + * Supported for images, video, and audio. Default: false + * @var boolean exif Deprecated. Use image_metadata instead + * @var boolean faces Include a list of coordinates of detected faces. Default: false + * @var boolean quality_analysis Include quality analysis information. Default: false + * @var boolean pages Report the number of pages in multi-page documents (e.g., PDF). Default: false + * @var boolean phash Include the perceptual hash (pHash) of the uploaded photo for image similarity + * detection. Default: false + * @var boolean coordinates Include previously specified custom cropping coordinates and faces + * coordinates. Default: false + * @var int max_results The number of derived images to return. Default: 10. Maximum: 100 + * @var string derived_next_cursor If there are more derived images than max_results, + * the derived_next_cursor value is returned as part of the response. You can then specify this value + * as the derived_next_cursor parameter of the following listing request. + * @var boolean cinemagraph_analysis Include cinemagraph analysis information. Default: false + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function resource($public_id, $options = array()) + { + $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); + $type = \Cloudinary::option_get($options, "type", "upload"); + $uri = array("resources", $resource_type, $type, $public_id); + $params = $this->only( + $options, + array( + "exif", + "colors", + "faces", + "quality_analysis", + "cinemagraph_analysis", + "image_metadata", + "phash", + "pages", + "coordinates", + "max_results", + "derived_next_cursor" + ) + ); + + return $this->call_api("get", $uri, $params, $options); + } + + /** + * Restores a deleted resource + * + * Reverts to the latest backed up version of the resource. + * + * @see https://cloudinary.com/documentation/admin_api#restore_a_deleted_resource + * + * @param string|array $public_ids The public IDs of (deleted or existing) backed up resources to restore. + * @param array $options { + * + * @var string resource_type The type of file. Default: image. Valid values: image, raw, video. + * @var string type The storage type: upload, private, or authenticated. Default: upload. + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function restore($public_ids, $options = array()) + { + $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); + $type = \Cloudinary::option_get($options, "type", "upload"); + $uri = array("resources", $resource_type, $type, "restore"); + $params = array_merge($options, array("public_ids" => $public_ids)); + + return $this->call_api("post", $uri, $params, $options); + } + + /** + * Updates details of an existing resource + * + * + * Update one or more of the attributes associated with a specified resource. Note that you can also update + * many attributes of an existing resource using the Uploader::explicit method, which is not rate limited. + * + * @see https://cloudinary.com/documentation/admin_api#update_details_of_an_existing_resource + * + * @param string|array $public_id The public ID of the resource to update. + * @param array $options { + * + * @var string resource_type The type of file. Default: image. + * Valid values: image, raw, video. + * @var string type The storage type. Default: upload. + * Valid values: upload, private, authenticated, facebook, twitter, gplus, instagram_name, gravatar, + * youtube, hulu, vimeo, animoto, worldstarhiphop or dailymotion + * @var string|array tags Tag names to assign to the uploaded image. + * @var string|array context Array of key-value pairs of general textual context metadata + * to attach to an uploaded resource. + * @var string|array face_coordinates Array of coordinates of faces contained in an uploaded image. + * Each face is specified by the X & Y coordinates of the top left corner + * and the width & height of the face. + * For example: array(array(10,20,150,130), array(213, 345, 82, 61)) + * @var string|array custom_coordinates Coordinates of an interesting region contained in an image. + * The given coordinates are used for cropping uploaded images using the custom gravity mode. + * The region is specified by the X & Y coordinates of the top left corner + * and the width & height of the region. For example: array(85, 120, 220, 310). + * @var string moderation_status Manually set image moderation status or override previously + * automatically moderated images by approving or rejecting. Valid values: approved, rejected + * @var float auto_tagging Whether to assign tags to an image according to detected scene + * categories with confidence score higher than the given value. Valid values: 0.0 to 1.0 + * @var string detection Set to 'adv_face' to automatically extract advanced face + * attributes of photos using the Advanced Facial Attributes Detection add-on + * @var string ocr Set to 'adv_ocr' to extract all text elements in an image + * as well as the bounding box coordinates of each detected elementusing the + * OCR Text Detection and Extraction add-on. + * @var string raw_convert Set to 'aspose' to automatically convert Office documents to + * PDF files and other image formats using the Aspose Document Conversion add-on. + * @var string categorization Set to 'imagga_tagging' to automatically detect scene categories + * of photos using the Imagga Auto Tagging add-on. + * @var string background_removal Set to 'remove_the_background' + * (or 'pixelz' - the new name of the company) to automatically clear the background of an uploaded + * photo using the Remove-The-Background Editing add-on. + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function update($public_id, $options = array()) + { + $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); + $type = \Cloudinary::option_get($options, "type", "upload"); + $uri = array("resources", $resource_type, $type, $public_id); + + $tags = \Cloudinary::option_get($options, "tags"); + $context = \Cloudinary::option_get($options, "context"); + $face_coordinates = \Cloudinary::option_get($options, "face_coordinates"); + $custom_coordinates = \Cloudinary::option_get($options, "custom_coordinates"); + $access_control = \Cloudinary::option_get($options, "access_control"); + + $primitive_options = $this->only( + $options, + array( + "moderation_status", + "raw_convert", + "ocr", + "categorization", + "detection", + "similarity_search", + "auto_tagging", + "background_removal", + "quality_override", + "notification_url", + ) + ); + + $array_options = array( + "tags" => $tags ? implode(",", \Cloudinary::build_array($tags)) : $tags, + "context" => $context ? \Cloudinary::encode_assoc_array($context) : $context, + "face_coordinates" => $face_coordinates ? \Cloudinary::encode_double_array( + $face_coordinates + ) : $face_coordinates, + "custom_coordinates" => $custom_coordinates ? \Cloudinary::encode_double_array( + $custom_coordinates + ) : $custom_coordinates, + "access_control" => \Cloudinary::encode_array_to_json($access_control), + ); + + $update_options = array_merge($primitive_options, $array_options); + + return $this->call_api("post", $uri, $update_options, $options); + } + + /** + * Deletes resources by public IDs + * + * Delete all resources with the given public IDs (up to 100). + * + * @see https://cloudinary.com/documentation/admin_api#delete_uploaded_images_by_public_ids + * + * @param string|array $public_ids The public IDs of the resources + * @param array $options { + * + * @var string resource_type The type of the file. Default: image. + * Valid values: image, raw, video. + * @var string type The storage type. Default: upload. + * Valid values: upload, private, authenticated, facebook, twitter, gplus, instagram_name, gravatar, + * youtube, hulu, vimeo, animoto, worldstarhiphop or dailymotion + * @var boolean keep_original Delete only the derived resources. Default: false + * @var boolean invalidate Whether to also invalidate the copies of the resource on the CDN + * Default: false + * @var string next_cursor When a deletion request has more than 1000 resources to delete, + * the response includes the partial boolean parameter set to true, as well as a next_cursor value. + * Use this returned next_cursor value as the next_cursor parameter of the following deletion request + * @var string|array transformations Only the derived resources matching this array of + * transformation parameters will be deleted. + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function delete_resources($public_ids, $options = array()) + { + $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); + $type = \Cloudinary::option_get($options, "type", "upload"); + $uri = array("resources", $resource_type, $type); + $params = $this->prepare_delete_resource_params($options, ["public_ids" => $public_ids]); + + return $this->call_api("delete", $uri, $params, $options); + } + + /** + * Deletes resources by prefix. + * + * Delete all resources, including derived resources, where the public ID starts with the given prefix + * (up to a maximum of 1000 original resources). + * + * @see https://cloudinary.com/documentation/admin_api#delete_uploaded_images_by_prefix + * + * @param string $prefix The prefix of the public IDs + * @param array $options { + * + * @option string resource_type The type of the file. Default: image. + * Valid values: image, raw, video. + * @option string type The storage type. Default: upload. + * Valid values: upload, private, authenticated, facebook, twitter, gplus, instagram_name, gravatar, + * youtube, hulu, vimeo, animoto, worldstarhiphop or dailymotion + * @option boolean keep_original Delete only the derived resources. Default: false + * @option boolean invalidate Whether to also invalidate the copies of the resource on the CDN + * Default: false + * @option string next_cursor When a deletion request has more than 1000 resources to delete, + * the response includes the partial boolean parameter set to true, as well as a next_cursor value. + * Use this returned next_cursor value as the next_cursor parameter of the following deletion request + * @option string|array transformations Only the derived resources matching this array of + * transformation parameters will be deleted. + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function delete_resources_by_prefix($prefix, $options = array()) + { + $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); + $type = \Cloudinary::option_get($options, "type", "upload"); + $uri = array("resources", $resource_type, $type); + $params = $this->prepare_delete_resource_params($options, ["prefix" => $prefix]); + + return $this->call_api("delete", $uri, $params, $options); + } + + /** + * Deletes all resources + * + * Delete all resources (of the relevant resource type and type), including derived resources + * (up to a maximum of 1000 original resources) + * + * @see https://cloudinary.com/documentation/admin_api#delete_all_or_selected_resources + * + * @param array $options { + * + * @var string resource_type The type of the file. Default: image. + * Valid values: image, raw, video. + * @var string type The storage type. Default: upload. + * Valid values: upload, private, authenticated, facebook, twitter, gplus, instagram_name, gravatar, + * youtube, hulu, vimeo, animoto, worldstarhiphop or dailymotion + * @var boolean keep_original Delete only the derived resources. Default: false + * @var boolean invalidate Whether to also invalidate the copies of the resource on the CDN + * Default: false + * @var string next_cursor When a deletion request has more than 1000 resources to delete, + * the response includes the partial boolean parameter set to true, as well as a next_cursor value. + * Use this returned next_cursor value as the next_cursor parameter of the following deletion request + * @var string|array transformations Only the derived resources matching this array of + * transformation parameters will be deleted. + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function delete_all_resources($options = array()) + { + $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); + $type = \Cloudinary::option_get($options, "type", "upload"); + $uri = array("resources", $resource_type, $type); + $params = $this->prepare_delete_resource_params($options, ["all" => true]); + + return $this->call_api("delete", $uri, $params, $options); + } + /** + * Deletes resources by tag + * + * Delete all resources (and their derivatives) with the given tag name + * (up to a maximum of 1000 original resources). + * + * @see https://cloudinary.com/documentation/admin_api#delete_resources_by_tags + * + * @param string $tag The tag name of the resources to delete + * @param array $options { + * + * @var string resource_type The type of the file. Default: image. + * Valid values: image, raw, video. + * @var boolean keep_original Delete only the derived resources. Default: false + * @var boolean invalidate Whether to also invalidate the copies of the resource on the CDN + * Default: false + * @var string next_cursor When a deletion request has more than 1000 resources to delete, + * the response includes the partial boolean parameter set to true, as well as a next_cursor value. + * Use this returned next_cursor value as the next_cursor parameter of the following deletion request + * @var string|array transformations Only the derived resources matching this array of + * transformation parameters will be deleted. + * } + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function delete_resources_by_tag($tag, $options = array()) + { + $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); + $uri = array("resources", $resource_type, "tags", $tag); + $params = $this->prepare_delete_resource_params($options); + + return $this->call_api("delete", $uri, $params, $options); + } + + /** + * Deletes derived resources + * + * Delete all derived resources with the given IDs (an array of up to 100 derived_resource_ids). + * The derived resource IDs are returned when calling the Details of a single resource method. + * + * @see https://cloudinary.com/documentation/admin_api#delete_derived_resources + * + * @param string|array $derived_resource_ids The derived resource IDs + * @param array $options Additional options + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function delete_derived_resources($derived_resource_ids, $options = array()) + { + $uri = array("derived_resources"); + $params = array("derived_resource_ids" => $derived_resource_ids); + + return $this->call_api("delete", $uri, $params, $options); + } + + /** + * Deletes derived resources identified by transformation for the provided public_ids + * + * @param string|array $public_ids The resources the derived resources belong to + * @param string|array $transformations The transformation(s) associated with the derived resources + * @param array $options { + * + * @var string resource_type The type of the file. Default: image. + * Valid values: image, raw, video. + * @var string type The storage type. Default: upload. + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function delete_derived_by_transformation( + $public_ids, + $transformations = array(), + $options = array() + ) { + $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); + $type = \Cloudinary::option_get($options, "type", "upload"); + $uri = ["resources", $resource_type, $type]; + $params = [ + "public_ids" => \Cloudinary::build_array($public_ids), + "keep_original" => true, + ]; + $params["transformations"] = \Cloudinary::build_eager($transformations); + $params = array_merge($params, $this->only($options, ["invalidate"])); + + return $this->call_api("delete", $uri, $params, $options); + } + + /** + * Lists tags + * + * @see https://cloudinary.com/documentation/admin_api#list_tags + * + * @param array $options { + * + * @var string resource_type The type of the file. Default: image. + * Valid values: image, raw, video. + * @var string prefix Find all tags that start with the given prefix. + * @var int max_results Max number of tags to return. Default: 10. Maximum: 500 + * @var string next_cursor When a listing request has more results to return than max_results, + * the next_cursor value is returned as part of the response. You can then specify this value as + * the next_cursor parameter of the following listing request. + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function tags($options = array()) + { + $resource_type = \Cloudinary::option_get($options, "resource_type", "image"); + $uri = array("tags", $resource_type); + $params = $this->only($options, array("next_cursor", "max_results", "prefix")); + + return $this->call_api("get", $uri, $params, $options); + } + + /** + * Lists transformations + * + * @see https://cloudinary.com/documentation/admin_api#list_transformations + * + * @param array $options { + * + * @var int max_results Max number of transformations to return. Default: 10. Maximum: 500 + * @var string next_cursor When a listing request has more results to return than max_results, + * the next_cursor value is returned as part of the response. You can then specify this value as + * the next_cursor parameter of the following listing request. + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function transformations($options = array()) + { + $uri = array("transformations"); + $params = $this->only($options, array("named", "next_cursor", "max_results")); + + return $this->call_api("get", $uri, $params, $options); + } + + /** + * Details of a single transformation + * + * @see https://cloudinary.com/documentation/admin_api#details_of_a_single_transformation + * + * @param string|array $transformation The transformation. Can be either a string or an array of parameters. + * For example: "w_150,h_100,c_fill" or array("width" => 150, "height" => 100,"crop" => "fill") + * @param array $options { + * + * @var int max_results Max number of transformations to return. Default: 10. Maximum: 500 + * @var string next_cursor When a listing request has more results to return than max_results, + * the next_cursor value is returned as part of the response. You can then specify this value as the + * next_cursor parameter of the following listing request. + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function transformation($transformation, $options = array()) + { + $uri = array("transformations"); + $params = $this->only($options, array("next_cursor", "max_results")); + $params["transformation"] = \Cloudinary::build_single_eager($transformation); + + return $this->call_api("get", $uri, $params, $options); + } + + /** + * Deletes transformation + * + * Note: Deleting a transformation also deletes all the derived images based on this transformation (up to 1000) + * The method returns an error if there are more than 1000 derived images based on this transformation. + * + * @see https://cloudinary.com/documentation/admin_api#delete_transformation + * + * @param string|array $transformation The transformation. Can be either a string or an array of parameters + * For example: "w_150,h_100,c_fill" or array("width" => 150, "height" => 100,"crop" => "fill") + * @param array $options Additional options + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function delete_transformation($transformation, $options = array()) + { + $uri = array("transformations"); + $params = array("transformation" => \Cloudinary::build_single_eager($transformation)); + if (isset($options["invalidate"])) { + $params["invalidate"] = $options["invalidate"]; + } + + return $this->call_api("delete", $uri, $params, $options); + } + + /** + * Updates transformation + * + * @see https://cloudinary.com/documentation/admin_api#update_transformation + * + * @param string|array $transformation The transformation. Can be either a string or an array of parameters. + * For example: "w_150,h_100,c_fill" or array("width" => 150, "height" => 100,"crop" => "fill") + * @param array $updates { + * + * @var boolean allowed_for_strict Whether this transformation is allowed when + * Strict Transformations are enabled. + * @var boolean unsafe_update Allows updating an existing named transformation without updating + * all associated derived images (the new settings of the named transformation only take effect from + * now on). + * } + * @param array $options Additional options + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function update_transformation($transformation, $updates = array(), $options = array()) + { + $uri = array("transformations"); + $params = $this->only($updates, array("allowed_for_strict")); + if (isset($updates["unsafe_update"])) { + $params["unsafe_update"] = $this->transformation_string($updates["unsafe_update"]); + } + $params["transformation"] = \Cloudinary::build_single_eager($transformation); + + return $this->call_api("put", $uri, $params, $options); + } + + /** + * Creates named transformation + * + * @see https://cloudinary.com/documentation/admin_api#create_named_transformation + * + * @param string $name The name of the transformation + * @param string|array $definition The transformation. Can be either a string or an array of parameters. + * For example: "w_150,h_100,c_fill" or array("width" => 150, "height" => 100,"crop" => "fill") + * @param array $options Additional options + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function create_transformation($name, $definition, $options = array()) + { + $uri = array("transformations"); + $params = array("transformation" => \Cloudinary::build_single_eager($definition), "name" => $name); + + return $this->call_api("post", $uri, $params, $options); + } + + /** + * Lists upload presets + * + * @see https://cloudinary.com/documentation/admin_api#list_upload_presets + * + * @param array $options { + * + * @var int max_results Max number of upload presets to return. Default: 10. Maximum: 500 + * @var string next_cursor When a listing request has more results to return than max_results, + * the next_cursor value is returned as part of the response. You can then specify this value as the + * next_cursor parameter of the following listing request. + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function upload_presets($options = array()) + { + $uri = array("upload_presets"); + $params = $this->only($options, array("next_cursor", "max_results")); + + return $this->call_api("get", $uri, $params, $options); + } + + /** + * Details of a single upload preset + * + * @see https://cloudinary.com/documentation/admin_api#details_of_a_single_upload_preset + * + * @param string $name The name of the upload preset + * @param array $options Additional options + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function upload_preset($name, $options = array()) + { + $uri = array("upload_presets", $name); + + return $this->call_api("get", $uri, $this->only($options, array("max_results")), $options); + } + + /** + * Deletes an upload preset + * + * @see https://cloudinary.com/documentation/admin_api#delete_an_upload_preset + * + * @param string $name The name of the upload preset + * @param array $options Additional options + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function delete_upload_preset($name, $options = array()) + { + $uri = array("upload_presets", $name); + + return $this->call_api("delete", $uri, array(), $options); + } + + /** + * Updates an upload preset + * + * @see https://cloudinary.com/documentation/admin_api#update_an_upload_preset + * + * @param string $name The name of the upload preset + * + * @see \Cloudinary\Uploader::upload() + * + * @param array $options { + * In addition to the options below, any Uploader::upload() actions to apply to assets uploaded with this + * preset. + * + * @var boolean unsigned Whether this upload preset allows unsigned uploading to Cloudinary. + * @var boolean disallow_public_id Whether this upload preset disables assigning a public_id in the + * image upload call + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function update_upload_preset($name, $options = array()) + { + $uri = array("upload_presets", $name); + $params = \Cloudinary\Uploader::build_upload_params($options); + $params = array_merge($params, $this->only($options, array("unsigned", "disallow_public_id", "live"))); + + return $this->call_api("put", $uri, $params, $options); + } + + /** + * Creates an upload preset + * + * @see https://cloudinary.com/documentation/admin_api#create_an_upload_preset + * + * @see \Cloudinary\Uploader::upload() + * + * @param array $options { + * In addition to the options below, any Uploader::upload() parameters to apply to assets uploaded with + * this preset. + * + * @var string name The name to assign to the new upload preset. If not provided, random + * name is generated + * @var boolean unsigned Whether this upload preset allows unsigned uploading to Cloudinary. + * @var boolean disallow_public_id Whether this upload preset disables assigning a public_id in the + * image upload call + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function create_upload_preset($options = array()) + { + $params = \Cloudinary\Uploader::build_upload_params($options); + $params = array_merge( + $params, + $this->only($options, array("name", "unsigned", "disallow_public_id", "live")) + ); + + return $this->call_api("post", array("upload_presets"), $params, $options); + } + + /** + * Lists all root folders + * + * @see https://cloudinary.com/documentation/admin_api#list_all_root_folders + * + * @param array $options { + * + * @var int max_results Max number of root folders to return. Default: 2000. Maximum: 2000 + * @var string next_cursor When a listing request has more results to return than max_results, + * the next_cursor value is returned as part of the response. You can then specify this value as the + * next_cursor parameter of the following listing request. + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function root_folders($options = array()) + { + $params = $this->only($options, array("next_cursor", "max_results")); + + return $this->call_api("get", array("folders"), $params, $options); + } + + /** + * Lists subfolders + * + * Lists the name and path of all the subfolders of a given root folder + * + * @see https://cloudinary.com/documentation/admin_api#list_subfolders + * + * @param string $of_folder_path The root folder + * @param array $options { + * + * @var int max_results Max number of sub-folders to return. Default: 2000. Maximum: 2000 + * @var string next_cursor When a listing request has more results to return than max_results, + * the next_cursor value is returned as part of the response. You can then specify this value as the + * next_cursor parameter of the following listing request. + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function subfolders($of_folder_path, $options = array()) + { + $uri = array("folders", $of_folder_path); + $params = $this->only($options, array("next_cursor", "max_results")); + + return $this->call_api("get", $uri, $params, $options); + } + + /** + * Creates folder + * + * Creates an empty folder + * + * @param string $path The folder path to create + * @param array $options Additional options + * + * @return Api\Response + * + * @throws Api\BadRequest + * @throws Api\GeneralError + */ + public function create_folder($path, $options = array()) + { + $uri = array("folders", $path); + + return $this->call_api("post", $uri, array(), $options); + } + + /** + * Deletes folder + * + * Deleted folder must be empty, but can have descendant empty sub folders + * + * @param string $path The folder to delete + * @param array $options Additional options + * + * @return Api\Response + * + * @throws Api\BadRequest + * @throws Api\GeneralError + */ + public function delete_folder($path, $options = array()) + { + $uri = array("folders", $path); + + return $this->call_api("delete", $uri, array(), $options); + } + + /** + * Lists upload mappings + * + * List all upload mappings by folder and its mapped template (URL). + * + * @see https://cloudinary.com/documentation/admin_api#list_upload_mappings + * + * @param array $options { + * + * @var int max_results Max number of upload presets to return. Default: 10. Maximum: 500 + * @var string next_cursor When a listing request has more results to return than max_results, + * the next_cursor value is returned as part of the response. You can then specify this value as the + * next_cursor parameter of the following listing request. + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function upload_mappings($options = array()) + { + $uri = array("upload_mappings"); + $params = $this->only($options, array("next_cursor", "max_results")); + + return $this->call_api("get", $uri, $params, $options); + } + + /** + * Details of a single upload mapping + * + * Retrieve the mapped template (URL) of a given upload mapping folder. + * + * @see https://cloudinary.com/documentation/admin_api#details_of_a_single_upload_mapping + * + * @param string $name The name of the folder + * @param array $options Additional options + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function upload_mapping($name, $options = array()) + { + $uri = array("upload_mappings"); + $params = array("folder" => $name); + + return $this->call_api("get", $uri, $params, $options); + } + + /** + * Deletes an upload mapping + * + * Delete an upload mapping by folder name. + * + * @see https://cloudinary.com/documentation/admin_api#delete_an_upload_mapping + * + * @param string $name The name of the folder + * @param array $options Additional options + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function delete_upload_mapping($name, $options = array()) + { + $uri = array("upload_mappings"); + $params = array("folder" => $name); + + return $this->call_api("delete", $uri, $params, $options); + } + + /** + * Updates an upload mapping + * + * Update an existing upload mapping folder with a new template (URL). + * + * @see https://cloudinary.com/documentation/admin_api#update_an_upload_mapping + * + * @param string $name The name of the folder + * @param array $options { + * + * @var string template The new URL to be mapped to the folder. + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function update_upload_mapping($name, $options = array()) + { + $uri = array("upload_mappings"); + $params = array_merge(array("folder" => $name), $this->only($options, array("template"))); + + return $this->call_api("put", $uri, $params, $options); + } + + /** + * Creates an upload mapping + * + * Create a new upload mapping folder and its template (URL). + * + * @see https://cloudinary.com/documentation/admin_api#create_an_upload_mapping + * + * @param string $name The name of the folder to map. + * @param array $options { + * + * @var string template The URL to be mapped to the folder. + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function create_upload_mapping($name, $options = array()) + { + $uri = array("upload_mappings"); + $params = array_merge(array("folder" => $name), $this->only($options, array("template"))); + + return $this->call_api("post", $uri, $params, $options); + } + + /** + * Lists streaming profiles + * + * List streaming profiles associated with the current customer, including built-in and custom profiles. + * + * @see https://cloudinary.com/documentation/admin_api#list_streaming_profiles + * + * @param array $options Additional options + * + * @return Api\Response An array with a "data" key for results + * + * @throws Api\GeneralError + */ + public function list_streaming_profiles($options = array()) + { + return $this->call_api("get", array("streaming_profiles"), array(), $options); + } + + /** + * Gets details of a single streaming profile + * + * Retrieve the details of a single streaming profile by name. + * + * @see https://cloudinary.com/documentation/admin_api#get_details_of_a_single_streaming_profile + * + * @param string $name The identification name of the streaming profile + * @param array $options Additional options + * + * @return Api\Response An array with a "data" key for results + * + * @throws Api\GeneralError + */ + public function get_streaming_profile($name, $options = array()) + { + $uri = array("streaming_profiles/" . $name); + return $this->call_api("get", $uri, array(), $options); + } + + /** + * Deletes or reverts the specified streaming profile + * + * For custom streaming profiles, delete the specified profile. + * For built-in streaming profiles, if the built-in profile was modified, revert the profile to the original + * settings. + * For built-in streaming profiles that have not been modified, the Delete method returns an error. + * + * @see https://cloudinary.com/documentation/admin_api#delete_or_revert_the_specified_streaming_profile + * + * @param string $name The identification name of the streaming profile + * @param array $options Additional options + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function delete_streaming_profile($name, $options = array()) + { + $uri = array("streaming_profiles/" . $name); + return $this->call_api("delete", $uri, array(), $options); + } + + /** + * Updates an existing streaming profile + * + * Update the specified existing streaming profile. You can update both custom and built-in profiles. + * The specified list of representations replaces the previous list. + * + * @param string $name The identification name of the streaming profile + * @param array $options { + * + * @var string display_name A descriptive name for the profile. + * @var array representations An array of structures that defines a custom streaming profile. + * @var string|array transformation Specifies the transformation parameters for the representation. + * All video transformation parameters except video_sampling are supported. Common transformation + * parameters for representations include: width, height (or aspect_ratio), bit_rate, video_codec, + * audio_codec, sample_rate (or fps), etc. + * @see self::create_transformation() + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function update_streaming_profile($name, $options = array()) + { + $uri = array("streaming_profiles/" . $name); + $params = $this->prepare_streaming_profile_params($options); + return $this->call_api("put", $uri, $params, $options); + } + + /** + * Creates a new, custom streaming profile. + * + * @see https://cloudinary.com/documentation/admin_api#create_a_streaming_profile + * + * @param string $name The identification name to assign to the new streaming profile. + * The name is case-insensitive and can contain alphanumeric characters, underscores (_) and hyphens (-). + * If the name is of a predefined profile, the profile will be modified. + * @param array $options { + * + * @var string display_name A descriptive name for the profile. + * @var array representations An array of structures that defines a custom streaming profile. + * @var string|array transformation Specifies the transformation parameters for the representation. + * All video transformation parameters except video_sampling are supported. Common transformation + * parameters for representations include: width, height (or aspect_ratio), bit_rate, video_codec, + * audio_codec, sample_rate (or fps), etc. + * @see self::create_transformation() + * } + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function create_streaming_profile($name, $options = array()) + { + $uri = array("streaming_profiles"); + $params = $this->prepare_streaming_profile_params($options); + $params["name"] = $name; + + return $this->call_api("post", $uri, $params, $options); + } + + /** + * The core function that performs the API call + * + * Function validates configuration, builds query string/request body, performs request and returns result + * + * @param string $method The HTTP method. Valid methods: get, post, put, delete + * @param array $uri REST endpoint of the API + * @param array $params Query/body parameters passed to the method + * @param array $options Additional options. Can be an override of the configuration, headers, etc. + * + * @return Api\Response + * + * @throws Api\GeneralError + */ + public function call_api($method, $uri, $params, &$options) + { + $prefix = \Cloudinary::option_get( + $options, + "upload_prefix", + \Cloudinary::config_get("upload_prefix", "https://api.cloudinary.com") + ); + $cloud_name = \Cloudinary::option_get($options, "cloud_name", \Cloudinary::config_get("cloud_name")); + if (!$cloud_name) { + throw new \InvalidArgumentException("Must supply cloud_name"); + } + $api_key = \Cloudinary::option_get($options, "api_key", \Cloudinary::config_get("api_key")); + if (!$api_key) { + throw new \InvalidArgumentException("Must supply api_key"); + } + $api_secret = \Cloudinary::option_get($options, "api_secret", \Cloudinary::config_get("api_secret")); + if (!$api_secret) { + throw new \InvalidArgumentException("Must supply api_secret"); + } + + $api_url = implode("/", array_merge(array($prefix, "v1_1", $cloud_name), array_map('rawurlencode', $uri))); + + $params = array_filter( + $params, + function ($v) { + return !is_null($v) && ($v !== ""); + } + ); + if ($method == "get") { + $api_url .= "?" . preg_replace("/%5B\d+%5D/", "%5B%5D", http_build_query($params)); + } + + $ch = curl_init($api_url); + + if ($method != "get") { + $post_params = array(); + if (array_key_exists("content_type", $options) && $options["content_type"] == 'application/json') { + $headers = array( + "Content-type: application/json", + "Accept: application/json", + ); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + $post_params = json_encode($params); + } else { + foreach ($params as $key => $value) { + if (is_array($value)) { + $i = 0; + foreach ($value as $item) { + $post_params[$key . "[$i]"] = $item; + $i++; + } + } else { + $post_params[$key] = $value; + } + } + } + curl_setopt($ch, CURLOPT_POSTFIELDS, $post_params); + } + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($method)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 60); + curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + curl_setopt($ch, CURLOPT_USERPWD, "{$api_key}:{$api_secret}"); + curl_setopt($ch, CURLOPT_CAINFO, realpath(dirname(__FILE__)) . DIRECTORY_SEPARATOR . "cacert.pem"); + curl_setopt($ch, CURLOPT_USERAGENT, \Cloudinary::userAgent()); + curl_setopt( + $ch, + CURLOPT_PROXY, + \Cloudinary::option_get($options, "api_proxy", \Cloudinary::config_get("api_proxy")) + ); + $response = $this->execute($ch); + $curl_error = null; + if (curl_errno($ch)) { + $curl_error = curl_error($ch); + } + curl_close($ch); + if ($curl_error != null) { + throw new \Cloudinary\Api\GeneralError("Error in sending request to server - " . $curl_error); + } + if ($response->responseCode == 200) { + return new \Cloudinary\Api\Response($response); + } else { + $exception_class = \Cloudinary::option_get( + self::$CLOUDINARY_API_ERROR_CLASSES, + $response->responseCode + ); + if (!$exception_class) { + throw new \Cloudinary\Api\GeneralError( + "Server returned unexpected status code - {$response->responseCode} - {$response->body}" + ); + } + $json = $this->parse_json_response($response); + throw new $exception_class($json["error"]["message"]); + } + } + + + /** + * Executes HTTP request, parses response headers, leaves body as a string + * + * Based on http://snipplr.com/view/17242/ + * + * @param resource $ch cURL handle + * + * @return \stdClass Containing headers, body, responseCode properties + */ + protected function execute($ch) + { + $string = curl_exec($ch); + $headers = array(); + $content = ''; + $str = strtok($string, "\n"); + $h = null; + while ($str !== false) { + if ($h and trim($str) === '') { + $h = false; + continue; + } + if ($h !== false and false !== strpos($str, ':')) { + $h = true; + list($headername, $headervalue) = explode(':', trim($str), 2); + $headervalue = ltrim($headervalue); + if (isset($headers[$headername])) { + $headers[$headername] .= ',' . $headervalue; + } else { + $headers[$headername] = $headervalue; + } + } + if ($h === false) { + $content .= $str . "\n"; + } + $str = strtok("\n"); + } + $result = new \stdClass; + $result->headers = $headers; + $result->body = trim($content); + $result->responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + return $result; + } + + /** + * Parses JSON string from response body. + * + * @param \stdClass $response Class representing response + * @see \Cloudinary\Api::execute() + * + * @return mixed Decoded JSON object + * + * @throws Api\GeneralError + */ + public static function parse_json_response($response) + { + $result = json_decode($response->body, true); + if ($result == null) { + $error = json_last_error(); + throw new \Cloudinary\Api\GeneralError( + "Error parsing server response ({$response->responseCode}) - {$response->body}. Got - {$error}" + ); + } + + return $result; + } + + /** + * Filters associative array using provided keys + * + * @param array $hash Array to filter + * @param array $keys Keys to keep + * + * @return array Filtered associative array + * + * @todo Replace with array_intersect_key($hash, array_flip($hash)) + */ + protected function only(&$hash, $keys) + { + $result = array(); + foreach ($keys as $key) { + if (isset($hash[$key])) { + $result[$key] = $hash[$key]; + } + } + + return $result; + } + + /** + * Alias for \Cloudinary::generate_transformation_string() + * + * @see \Cloudinary::generate_transformation_string() + * + * @param string|array $transformation + * + * @return string Resulting transformation string + */ + protected function transformation_string($transformation) + { + if (is_string($transformation)) { + return $transformation; + } + + return \Cloudinary::generate_transformation_string($transformation); + } + + /** + * Prepares streaming profile parameters for API calls + * + * @param array $options The options passed to the API + * + * @return array A single profile parameters + */ + protected function prepare_streaming_profile_params($options) + { + $params = $this->only($options, array("display_name")); + if (isset($options['representations'])) { + $array_map = array_map( + function ($representation) { + return array("transformation" => \Cloudinary::generate_transformation_string($representation)); + }, + $options['representations'] + ); + $params["representations"] = json_encode($array_map); + } + + return $params; + } + + /** + * Prepares delete resource parameters for API calls + * + * @param array $options Additional options + * @param array $params The parameters passed to the API + * + * @return array Updated parameters + */ + protected function prepare_delete_resource_params($options, $params = []) + { + $filtered = $this->only($options, ["keep_original", "next_cursor", "invalidate"]); + if (isset($options["transformations"])) { + $filtered["transformations"] = \Cloudinary::build_eager($options["transformations"]); + } + return array_merge($params, $filtered); + } + } + +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Api/AlreadyExists.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Api/AlreadyExists.php new file mode 100644 index 0000000..c6da55a --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Api/AlreadyExists.php @@ -0,0 +1,11 @@ +rate_limit_reset_at = strtotime($response->headers["X-FeatureRateLimit-Reset"]); + $this->rate_limit_allowed = intval($response->headers["X-FeatureRateLimit-Limit"]); + $this->rate_limit_remaining = intval($response->headers["X-FeatureRateLimit-Remaining"]); + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/AuthToken.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/AuthToken.php new file mode 100644 index 0000000..3979ea5 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/AuthToken.php @@ -0,0 +1,92 @@ +?@\[\]^`{\|}~\\\\])/'; + + /** + * Generate an authorization token. + * Options: + * string key - the secret key required to sign the token + * string ip - the IP address of the client + * number start_time - the start time of the token in seconds from epoch + * string expiration - the expiration time of the token in seconds from epoch + * string duration - the duration of the token (from start_time) + * string acl - the ACL for the token + * string url - the URL to authentication in case of a URL token + * + * @param array $options token configuration + * + * @return string the authorization token + * @throws Error if both expiration and duration were not provided + */ + public static function generate($options = array()) + { + $key = \Cloudinary::option_get($options, "key"); + if (!isset($key)) { + throw new \Cloudinary\Error("Missing authentication token key configuration"); + } + $name = \Cloudinary::option_get($options, "token_name", "__cld_token__"); + $start = \Cloudinary::option_get($options, "start_time"); + $expiration = \Cloudinary::option_get($options, "expiration"); + $ip = \Cloudinary::option_get($options, "ip"); + $acl = \Cloudinary::option_get($options, "acl"); + $url = \Cloudinary::option_get($options, "url"); + $duration = \Cloudinary::option_get($options, "duration"); + + if (!strcasecmp($start, "now")) { + $start = time(); + } elseif (is_numeric($start)) { + $start = 0 + $start; + } + if (!isset($expiration)) { + if (isset($duration)) { + $expiration = (isset($start) ? $start : time()) + $duration; + } else { + throw new \Cloudinary\Error("Must provide 'expiration' or 'duration'."); + } + } + $token = array(); + if (isset($ip)) { + array_push($token, "ip=$ip"); + } + if (isset($start)) { + array_push($token, "st=$start"); + } + array_push($token, "exp=$expiration"); + if (isset($acl)) { + array_push($token, "acl=" . self::escape_to_lower($acl)); + } + $to_sign = $token; + if (isset($url) && !isset($acl)) { + array_push($to_sign, "url=" . self::escape_to_lower($url)); + } + $auth = self::digest(join("~", $to_sign), $key); + array_push($token, "hmac=$auth"); + + return "$name=" . join("~", $token); + } + + private static function digest($message, $key = null) + { + if (!isset($key)) { + $key = \Cloudinary::config_get("akamai_key"); + } + $bin_key = pack("H*", $key); + + return hash_hmac("sha256", $message, $bin_key); + } + + private static function escape_to_lower($url) + { + return preg_replace_callback(self::UNSAFE, function ($match) { + return '%'.bin2hex($match[0]); + }, $url); + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Cache/Adapter/CacheAdapter.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Cache/Adapter/CacheAdapter.php new file mode 100644 index 0000000..896cc4c --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Cache/Adapter/CacheAdapter.php @@ -0,0 +1,56 @@ +setKeyValueStorage($storage); + } + + /** + * Set the storage interface + * @param object $storage KeyValueStorage or PSR-16 compliant cache + * + * @return bool true if successful + */ + private function setKeyValueStorage($storage) + { + if (!is_object($storage)) { + throw new InvalidArgumentException("An instance of valid storage must be provided"); + } + + $storageClasses = class_implements($storage); + $validStorages = ['Cloudinary\Cache\Storage\KeyValueStorage', 'Psr\SimpleCache\CacheInterface']; + + $found = count(\Cloudinary::array_subset($storageClasses, $validStorages)) > 0; + + if (!$found) { + throw new InvalidArgumentException("An instance of valid storage must be provided"); + } + + $this->keyValueStorage = $storage; + + return true; + } + + /** + * {@inheritdoc} + */ + public function get($publicId, $type, $resourceType, $transformation, $format) + { + return json_decode( + $this->keyValueStorage->get( + self::generateCacheKey($publicId, $type, $resourceType, $transformation, $format) + ) + ); + } + + /** + * {@inheritdoc} + */ + public function set($publicId, $type, $resourceType, $transformation, $format, $value) + { + return $this->keyValueStorage->set( + self::generateCacheKey($publicId, $type, $resourceType, $transformation, $format), + json_encode($value) + ); + } + + /** + * {@inheritdoc} + */ + public function delete($publicId, $type, $resourceType, $transformation, $format) + { + return $this->keyValueStorage->delete( + self::generateCacheKey($publicId, $type, $resourceType, $transformation, $format) + ); + } + + /** + * {@inheritdoc} + */ + public function flushAll() + { + return $this->keyValueStorage->clear(); + } + + /** + * Generates key-value storage key from parameters + * + * @param string $publicId The public ID of the resource + * @param string $type The storage type + * @param string $resourceType The type of the resource + * @param string $transformation The transformation string + * @param string $format The format of the resource + * + * @return string Resulting cache key + */ + public static function generateCacheKey($publicId, $type, $resourceType, $transformation, $format) + { + return sha1(implode("/", array_filter([$publicId, $type, $resourceType, $transformation, $format]))); + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Cache/ResponsiveBreakpointsCache.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Cache/ResponsiveBreakpointsCache.php new file mode 100644 index 0000000..a7eed0b --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Cache/ResponsiveBreakpointsCache.php @@ -0,0 +1,156 @@ +init(); + } + + /** + * Initialize the cache + * @param array $cacheOptions Cache configuration options. + */ + public function init($cacheOptions = array()) + { + $cacheAdapter = \Cloudinary::option_get($cacheOptions, "cache_adapter"); + + $this->setCacheAdapter($cacheAdapter); + } + + /** + * Assigns cache adapter + * + * @param CacheAdapter $cacheAdapter The cache adapter used to store and retrieve values. + * + * @return bool Returns true if the $cacheAdapter is valid + */ + public function setCacheAdapter($cacheAdapter) + { + if (is_null($cacheAdapter) || ! $cacheAdapter instanceof CacheAdapter) { + return false; + } + + $this->cacheAdapter = $cacheAdapter; + + return true; + } + + /** + * Indicates whether cache is enabled or not + * + * @return bool true if a $cach adapter has been set. + */ + public function enabled() + { + return !is_null($this->cacheAdapter); + } + + /** + * Extract the parameters required in order to calculate the key of the cache. + * + * @param array $options Input options + * + * @return array A list of values used to calculate the cache key. + */ + private static function optionsToParameters($options) + { + $optionsCopy = \Cloudinary::array_copy($options); + $transformation = \Cloudinary::generate_transformation_string($optionsCopy); + $format = \Cloudinary::option_get($options, "format", ""); + $type = \Cloudinary::option_get($options, "type", "upload"); + $resourceType = \Cloudinary::option_get($options, "resource_type", "image"); + + return [$type, $resourceType, $transformation, $format]; + } + + /** + * Retrieve the breakpoints of a particular derived resource identified by the $publicId and $options + * + * @param string $publicId The public ID of the resource + * @param array $options Additional options + * + * @return array|null Array of responsive breakpoints, null if not found + */ + public function get($publicId, $options = []) + { + if (!$this->enabled()) { + return null; + } + + list($type, $resourceType, $transformation, $format) = self::optionsToParameters($options); + + return $this->cacheAdapter->get($publicId, $type, $resourceType, $transformation, $format); + } + + /** + * Sets responsive breakpoints identified by public ID and options + * + * @param string $publicId The public ID of the resource + * @param array $options Additional options + * @param array $value Array of responsive breakpoints to set + * + * @return bool true on success or false on failure + */ + public function set($publicId, $options = [], $value = []) + { + if (!$this->enabled()) { + return false; + } + + if (! is_array($value)) { + throw new InvalidArgumentException("An array of breakpoints is expected"); + } + + list($type, $resourceType, $transformation, $format) = self::optionsToParameters($options); + + return $this->cacheAdapter->set($publicId, $type, $resourceType, $transformation, $format, $value); + } + + /** + * Delete responsive breakpoints identified by public ID and options + * + * @param string $publicId The public ID of the resource + * @param array $options Additional options + * + * @return bool true on success or false on failure + */ + public function delete($publicId, $options = []) + { + if (!$this->enabled()) { + return false; + } + + list($type, $resourceType, $transformation, $format) = self::optionsToParameters($options); + + return $this->cacheAdapter->delete($publicId, $type, $resourceType, $transformation, $format); + } + + /** + * Flushe all entries from cache + * + * @return bool true on success or false on failure + */ + public function flushAll() + { + if (!$this->enabled()) { + return false; + } + + return $this->cacheAdapter->flushAll(); + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Cache/Storage/FileSystemKeyValueStorage.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Cache/Storage/FileSystemKeyValueStorage.php new file mode 100644 index 0000000..1989e70 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Cache/Storage/FileSystemKeyValueStorage.php @@ -0,0 +1,123 @@ +rootPath = $rootPath; + } + + /** + * {@inheritdoc} + */ + public function get($key) + { + if (!$this->exists($key)) { + return null; + } + + return file_get_contents($this->getKeyFullPath($key)); + } + + /** + * {@inheritdoc} + */ + public function set($key, $value) + { + $bytesWritten = file_put_contents($this->getKeyFullPath($key), $value); + + if ($bytesWritten === false) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function delete($key) + { + if (!$this->exists($key)) { + return true; + } + + return unlink($this->getKeyFullPath($key)); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $success = true; + + $cacheItems = new GlobIterator($this->rootPath . DIRECTORY_SEPARATOR . "*" . self::$itemExt); + + if (!$cacheItems->count()) { + return true; + } + + foreach ($cacheItems as $itemPath) { + if (!unlink($itemPath)) { + $success = false; + } + } + + return $success; + } + + /** + * Generate the file path for the $key. + * + * @param string $key + * + * @return string The absolute path of the value file associated with the $key. + */ + private function getKeyFullPath($key) + { + return $this->rootPath . DIRECTORY_SEPARATOR . $key . self::$itemExt; + } + + /** + * Indicate whether key exists + * + * @param string $key + * + * @return bool True if the file for the given $key exists. + */ + private function exists($key) + { + return file_exists($this->getKeyFullPath($key)); + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Cache/Storage/KeyValueStorage.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Cache/Storage/KeyValueStorage.php new file mode 100644 index 0000000..b02e167 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Cache/Storage/KeyValueStorage.php @@ -0,0 +1,43 @@ +(\d+\.)?\d+)(?P[%pP])?$/'; + const RANGE_RE = '/^(\d+\.)?\d+[%pP]?\.\.(\d+\.)?\d+[%pP]?$/'; + + const VERSION = "1.16.0"; + + /** + * @internal + * @var array a list of keys used by the cloudinary_url function + */ + public static $URL_KEYS = array( + 'api_secret', + 'auth_token', + 'cdn_subdomain', + 'cloud_name', + 'cname', + 'format', + 'private_cdn', + 'resource_type', + 'secure', + 'secure_cdn_subdomain', + 'secure_distribution', + 'shorten', + 'sign_url', + 'ssl_detected', + 'type', + 'url_suffix', + 'use_root_path', + 'version' + ); + + /** + * Contains information about SDK user agent. Passed to the Cloudinary servers. + * + * Initialized on the first call to {@see self::userAgent()} + * + * Sample value: CloudinaryPHP/1.2.3 (PHP 5.6.7) + * + * @internal + * Do not change this value + */ + private static $USER_AGENT = ""; + + /** + * Additional information to be passed with the USER_AGENT, e.g. "CloudinaryMagento/1.0.1". + * This value is set in platform-specific + * implementations that use cloudinary_php. + * + * The format of the value should be /Version[ (comment)]. + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 + * + * @internal + * Do not set this value in application code! + * + * @var string + */ + public static $USER_PLATFORM = ""; + + public static $DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION = array("width" => "auto", "crop" => "limit"); + + private static $config = null; + + public static $JS_CONFIG_PARAMS = array( + "api_key", + "cloud_name", + "private_cdn", + "secure_distribution", + "cdn_subdomain", + ); + + /** + * Provides the {@see self::$USER_AGENT} string that is passed to the Cloudinary servers. + * + * Prepends {@see self::$USER_PLATFORM} if it is defined. + * + * @return string + */ + public static function userAgent() + { + if (empty(self::$USER_AGENT)) { + self::$USER_AGENT = 'CloudinaryPHP/' . self::VERSION . ' (PHP ' . PHP_VERSION. ')'; + } + + if (empty(self::$USER_PLATFORM)) { + return self::$USER_AGENT; + } + + return self::$USER_PLATFORM . ' ' . self::$USER_AGENT; + } + + public static function is_not_null($var) + { + return !is_null($var); + } + + /** + * @internal + * When upload type is fetch, remove the format options. + * In addition, set the fetch_format options to the format value unless it was already set. + * Mutates the $options parameter! + * @param array $options URL and transformation options + */ + public static function patch_fetch_format(&$options) + { + $type = Cloudinary::option_get($options, "type", "upload"); + if ($type != "fetch") return; + + // format does not apply to fetch resources since they are identified by a URL + $format = Cloudinary::option_consume($options, "format"); + if (!isset($options["fetch_format"])) { + $options["fetch_format"] = $format; + } + } + + public static function config($values = null) + { + if (self::$config == null) { + self::reset_config(); + } + if ($values != null) { + self::$config = array_merge(self::$config, $values); + } + + return self::$config; + } + + public static function reset_config() + { + self::config_from_url(getenv("CLOUDINARY_URL")); + } + + public static function config_from_url($cloudinary_url) + { + self::$config = array(); + if ($cloudinary_url) { + $uri = parse_url($cloudinary_url); + + if (!isset($uri["scheme"]) || strtolower($uri["scheme"]) !== "cloudinary") { + throw new InvalidArgumentException("Invalid CLOUDINARY_URL scheme. Expecting to start with 'cloudinary://'"); + } + + $q_params = array(); + + if (isset($uri["query"])) { + parse_str($uri["query"], $q_params); + } + + $private_cdn = isset($uri["path"]) && $uri["path"] != "/"; + $config = array_merge( + $q_params, + array( + "cloud_name" => $uri["host"], + "api_key" => $uri["user"], + "api_secret" => $uri["pass"], + "private_cdn" => $private_cdn, + ) + ); + if ($private_cdn) { + $config["secure_distribution"] = substr($uri["path"], 1); + } + self::$config = array_merge(self::$config, $config); + } + } + + public static function config_get($option, $default = null) + { + return Cloudinary::option_get(self::config(), $option, $default); + } + + public static function option_get($options, $option, $default = null) + { + if (isset($options[$option])) { + return $options[$option]; + } else { + return $default; + } + } + + public static function option_consume(&$options, $option, $default = null) + { + $value = self::option_get($options, $option, $default); + unset($options[$option]); + + return $value; + } + + public static function build_array($value) + { + if (is_null($value)) { + return array(); + } + + if (is_array($value) && !Cloudinary::is_assoc($value)) { + return $value; + } + + return array($value); + } + + + /** + * Converts a value that can be presented as an array of associative arrays. + * + * In case top level item is not an array, it is wrapped with an array + * + * @param array|string $value The value to be converted + * + * Valid values examples: + * - Valid assoc array: array("k" => "v", "k2"=> "v2") + * - Array of assoc arrays: array(array("k" => "v"), array("k2" =>"v2")) + * - JSON decodable string: '{"k": "v"}', or '[{"k": "v"}]' + * + * Invalid values examples: + * - array("not", "an", "assoc", "array") + * - array(123, None), + * - array(array("another", "array")) + * + * @return array|mixed Converted(or original) array of associative arrays + * + * @throws InvalidArgumentException in case value cannot be converted to an array of associative arrays + */ + private static function build_array_of_assoc_arrays($value) + { + if (is_string($value)) { + $value = Cloudinary::json_decode_cb($value, 'Cloudinary::ensure_assoc'); + if (is_null($value)) { + throw new InvalidArgumentException("Failed parsing JSON string value"); + } + } + $value = Cloudinary::build_array($value); + if (!self::is_array_of_assoc($value)) { + throw new InvalidArgumentException("Expected an array of associative arrays"); + } + return $value; + } + + static function ensure_assoc($item) + { + if (is_string($item)) { + $item = json_decode($item, true); + if (is_null($item)) { + throw new InvalidArgumentException("Failed parsing JSON string item"); + } + } + + if (!Cloudinary::is_assoc($item)) { + throw new InvalidArgumentException("Expected an array of associative arrays"); + } + return $item; + } + /** + * Encodes an array of associative arrays to JSON. + * + * This is a wrapper around json_encode with additional preprocessing (DateTime formatting, etc) + * + * @param array $array An array of associative arrays + * + * @return string Resulting JSON string + * + * @throws InvalidArgumentException in case the value is not an array of associative arrays + */ + private static function json_encode_array_of_assoc_arrays($array) + { + return self::json_encode_cb($array, 'Cloudinary::encode_dates'); + } + + static function encode_dates($value) + { + if ($value instanceof DateTime) { + $value = $value->format(DateTime::ISO8601); + } + return $value; + } + + static function is_array_of_assoc($array) + { + if (!is_array($array)) { + return false; + } + + foreach ($array as &$item) { + if (!Cloudinary::is_assoc($item)) { + return false; + } + } + return true; + } + + /** + * Returns the JSON representation of a value + * @param mixed $array

+ * The value being encoded. Can be any type except + * a resource. + *

+ * @param callable $encoder [optional]

+ * An encoder that will be invoked for each value. + *

+ * @param int $options [optional]

+ * Bitmask consisting of JSON_HEX_QUOT, + * JSON_HEX_TAG, + * JSON_HEX_AMP, + * JSON_HEX_APOS, + * JSON_NUMERIC_CHECK, + * JSON_PRETTY_PRINT, + * JSON_UNESCAPED_SLASHES, + * JSON_FORCE_OBJECT, + * JSON_UNESCAPED_UNICODE. The behaviour of these + * constants is described on + * the JSON constants page. + *

+ * @param int $depth [optional]

+ * Set the maximum depth. Must be greater than zero. + *

+ * @return string a JSON encoded string on success or FALSE on failure. + */ + + public static function json_encode_cb($array, $encoder = null, $options = 0, $depth = 512) + { + if (!is_array($array)) { + throw new InvalidArgumentException("Expected an array of associative arrays"); + } + + foreach ($array as &$item) { + $is_assoc = Cloudinary::is_assoc($item); + if (!$is_assoc) { + throw new InvalidArgumentException("Expected an array of associative arrays"); + } + if (!is_null($encoder)) { + foreach ($item as $key => $value) { + $item[$key] = call_user_func($encoder, $value); + } + } + } + + return \json_encode($array); + } + + public static function json_decode_cb($json, $decoder) + { + if (!is_string($json)) { + throw new InvalidArgumentException("Expected an string"); + } + $array = json_decode($json, true); + if (!is_null($decoder) && !is_null($array)) { + foreach ($array as $key => $value) { + try { + $array[$key] = call_user_func($decoder, $value); + } catch (Exception $e) { + } + + } + } + + return $array; + } + + /** + * Wrapper for calling build_array_of_assoc_arrays and json_encode_array_of_assoc_arrays with null value handling. + * + * @see Cloudinary::json_encode_array_of_assoc_arrays + * @see Cloudinary::build_array_of_assoc_arrays + * + * @param array|string $value The value to be converted + * + * @return string Resulting JSON string + * + * @throws InvalidArgumentException in case value cannot be converted and encoded + */ + public static function encode_array_to_json($value) + { + if (is_null($value)) { + return null; + } + $array = Cloudinary::build_array_of_assoc_arrays($value); + return Cloudinary::json_encode_cb($array, 'Cloudinary::encode_dates'); + } + + public static function encode_array($array) + { + return implode(",", Cloudinary::build_array($array)); + } + + public static function encode_double_array($array) + { + $array = Cloudinary::build_array($array); + if (count($array) > 0 && !is_array($array[0])) { + return Cloudinary::encode_array($array); + } else { + $array = array_map('Cloudinary::encode_array', $array); + } + + return implode("|", $array); + } + + public static function encode_assoc_array($array) + { + if (Cloudinary::is_assoc($array)) { + $encoded = array(); + foreach ($array as $key => $value) { + if (!empty($value)) { + $value = preg_replace('/([\|=])/', '\\\$1', $value); + } + array_push($encoded, $key . '=' . $value); + } + + return implode("|", $encoded); + } else { + return $array; + } + } + + /** + * Encodes data with URL safe base64 + * + * @see https://tools.ietf.org/html/rfc4648#section-5 + * + * @param mixed $data The data to encode. + * + * @return string The encoded data, as a string. + */ + private static function base64url_encode($data) + { + return strtr(base64_encode($data), '+/', '-_'); + } + + /** + * Helper function for making a recursive array copy while cloning objects on the way. + * + * @param array $array Source array + * + * @return array Recursive copy of the source array + */ + public static function array_copy($array) + { + if (!is_array($array)) { + return $array; + } + + $result = array(); + foreach ($array as $key => $val) { + if (is_array($val)) { + $result[$key] = self::array_copy($val); + } elseif (is_object($val)) { + $result[$key] = clone $val; + } else { + $result[$key] = $val; + } + } + return $result; + } + + /** + * Returns subset of associative array specified by array of keys + * + * @param array $array Source associative array + * @param array $keys Simple array of keys + * + * @return array Resulting array + */ + public static function array_subset($array, $keys) + { + return array_intersect_key($array, array_flip($keys)); + } + + private static function is_assoc($array) + { + if (!is_array($array)) { + return false; + } + + return $array != array_values($array); + } + + /** @internal + * Prepends associative element to the beginning of an array + * + * @param array $arr The input array. + * @param mixed $key The prepended key + * @param mixed $val The prepended value + * + * @return array The resulting array + */ + public static function array_unshift_assoc(&$arr, $key, $val) + { + $arr = array_reverse($arr, true); + $arr[$key] = $val; + $arr = array_reverse($arr, true); + return $arr; + } + + private static function generate_base_transformation($base_transformation) + { + $options = is_array( + $base_transformation + ) ? $base_transformation : array("transformation" => $base_transformation); + + return Cloudinary::generate_transformation_string($options); + } + + // Warning: $options are being destructively updated! + public static function generate_transformation_string(&$options = array()) + { + $generate_base_transformation = "Cloudinary::generate_base_transformation"; + if (is_string($options)) { + return $options; + } + if ($options == array_values($options)) { + return implode("/", array_map($generate_base_transformation, $options)); + } + + $responsive_width = Cloudinary::option_consume( + $options, + "responsive_width", + Cloudinary::config_get("responsive_width") + ); + + $size = Cloudinary::option_consume($options, "size"); + if ($size) { + list($options["width"], $options["height"]) = preg_split("/x/", $size); + } + + $width = Cloudinary::option_get($options, "width"); + $height = Cloudinary::option_get($options, "height"); + + $has_layer = Cloudinary::option_get($options, "underlay") || Cloudinary::option_get($options, "overlay"); + $angle = implode(".", Cloudinary::build_array(Cloudinary::option_consume($options, "angle"))); + $crop = Cloudinary::option_consume($options, "crop"); + + $no_html_sizes = $has_layer || !empty($angle) || $crop == "fit" || $crop == "limit" || $responsive_width; + + if (strlen($width) == 0 || $width && + (substr($width, 0, 4) == "auto" || floatval($width) < 1 || $no_html_sizes)) { + unset($options["width"]); + } + if (strlen($height) == 0 || $height && (floatval($height) < 1 || $no_html_sizes)) { + unset($options["height"]); + } + + $background = Cloudinary::option_consume($options, "background"); + if ($background) { + $background = preg_replace("/^#/", 'rgb:', $background); + } + $color = Cloudinary::option_consume($options, "color"); + if ($color) { + $color = preg_replace("/^#/", 'rgb:', $color); + } + + $base_transformations = Cloudinary::build_array(Cloudinary::option_consume($options, "transformation")); + if (count(array_filter($base_transformations, "is_array")) > 0) { + $base_transformations = array_map($generate_base_transformation, $base_transformations); + $named_transformation = ""; + } else { + $named_transformation = implode(".", $base_transformations); + $base_transformations = array(); + } + + $effect = Cloudinary::option_consume($options, "effect"); + if (is_array($effect)) { + $effect = implode(":", $effect); + } + + $border = Cloudinary::process_border(Cloudinary::option_consume($options, "border")); + + $flags = implode(".", Cloudinary::build_array(Cloudinary::option_consume($options, "flags"))); + $dpr = Cloudinary::option_consume($options, "dpr", Cloudinary::config_get("dpr")); + + $duration = Cloudinary::norm_range_value(Cloudinary::option_consume($options, "duration")); + $start_offset = Cloudinary::norm_auto_range_value(Cloudinary::option_consume($options, "start_offset")); + $end_offset = Cloudinary::norm_range_value(Cloudinary::option_consume($options, "end_offset")); + $offset = Cloudinary::split_range(Cloudinary::option_consume($options, "offset")); + if (!empty($offset)) { + $start_offset = Cloudinary::norm_auto_range_value($offset[0]); + $end_offset = Cloudinary::norm_range_value($offset[1]); + } + + $video_codec = Cloudinary::process_video_codec_param(Cloudinary::option_consume($options, "video_codec")); + $fps = Cloudinary::process_fps(Cloudinary::option_consume($options, "fps")); + $keyframe_interval = Cloudinary::process_keyframe_interval(Cloudinary::option_consume($options, "keyframe_interval")); + + $overlay = Cloudinary::process_layer(Cloudinary::option_consume($options, "overlay"), "overlay"); + $underlay = Cloudinary::process_layer(Cloudinary::option_consume($options, "underlay"), "underlay"); + $if = Cloudinary::process_if(Cloudinary::option_consume($options, "if")); + $custom_function = Cloudinary::process_custom_function(Cloudinary::option_consume($options, "custom_function")); + $custom_pre_function = Cloudinary::process_custom_pre_function(Cloudinary::option_consume($options, "custom_pre_function")); + $aspect_ratio = Cloudinary::option_consume($options, "aspect_ratio"); + $opacity = Cloudinary::option_consume($options, "opacity"); + $quality = Cloudinary::option_consume($options, "quality"); + $radius = Cloudinary::process_radius(Cloudinary::option_consume($options, "radius")); + $x = Cloudinary::option_consume($options, "x"); + $y = Cloudinary::option_consume($options, "y"); + $zoom = Cloudinary::option_consume($options, "zoom"); + + $params = array( + "a" => self::normalize_expression($angle), + "ar" => self::normalize_expression($aspect_ratio), + "b" => $background, + "bo" => $border, + "c" => $crop, + "co" => $color, + "dpr" => self::normalize_expression($dpr), + "du" => $duration, + "e" => self::normalize_expression($effect), + "eo" => $end_offset, + "fl" => $flags, + "fn" => $custom_function ?: $custom_pre_function, + "fps" => $fps, + "ki" => $keyframe_interval, + "h" => self::normalize_expression($height), + "l" => $overlay, + "o" => self::normalize_expression($opacity), + "q" => self::normalize_expression($quality), + "r" => $radius, + "so" => $start_offset, + "t" => $named_transformation, + "u" => $underlay, + "vc" => $video_codec, + "w" => self::normalize_expression($width), + "x" => self::normalize_expression($x), + "y" => self::normalize_expression($y), + "z" => self::normalize_expression($zoom), + ); + + $simple_params = array( + "ac" => "audio_codec", + "af" => "audio_frequency", + "br" => "bit_rate", + "cs" => "color_space", + "d" => "default_image", + "dl" => "delay", + "dn" => "density", + "f" => "fetch_format", + "g" => "gravity", + "p" => "prefix", + "pg" => "page", + "sp" => "streaming_profile", + "vs" => "video_sampling", + ); + + foreach ($simple_params as $param => $option) { + $params[$param] = Cloudinary::option_consume($options, $option); + } + + $variables = !empty($options["variables"]) ? $options["variables"] : []; + + $var_params = []; + foreach ($options as $key => $value) { + if (preg_match('/^\$/', $key)) { + $var_params[] = $key . '_' . self::normalize_expression((string)$value); + } + } + + sort($var_params); + + if (!empty($variables)) { + foreach ($variables as $key => $value) { + $var_params[] = $key . '_' . self::normalize_expression((string)$value); + } + } + + $variables = join(',', $var_params); + + + $param_filter = function ($value) { + return $value === 0 || $value === '0' || trim($value) == true; + }; + $params = array_filter($params, $param_filter); + ksort($params); + if (isset($if)) { + $if = 'if_' . $if; + } + $join_pair = function ($key, $value) { + return $key . "_" . $value; + }; + $transformation = implode(",", array_map($join_pair, array_keys($params), array_values($params))); + $raw_transformation = Cloudinary::option_consume($options, "raw_transformation"); + $transformation = implode(",", array_filter(array($if, $variables, $transformation, $raw_transformation))); + array_push($base_transformations, $transformation); + if ($responsive_width) { + $responsive_width_transformation = Cloudinary::config_get( + "responsive_width_transformation", + Cloudinary::$DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION + ); + array_push( + $base_transformations, + Cloudinary::generate_transformation_string($responsive_width_transformation) + ); + } + if (substr($width, 0, 4) == "auto" || $responsive_width) { + $options["responsive"] = true; + } + if (substr($dpr, 0, 4) == "auto") { + $options["hidpi"] = true; + } + + return implode("/", array_filter($base_transformations)); + } + + /** + * Helper function, allows chaining transformations to the end of transformations list + * + * The result of this function is an updated $options parameter + * + * @param array $options Original options + * @param array $transformations Transformations to chain at the end + * + * @return array Resulting options + */ + public static function chain_transformations($options, $transformations) + { + $transformations = \Cloudinary::build_array($transformations); + // preserve url options + $url_options = self::array_subset($options, self::$URL_KEYS); + array_unshift($transformations, $options); + $url_options["transformation"] = $transformations; + return $url_options; + } + + private static $LAYER_KEYWORD_PARAMS = array( + "font_weight" => "normal", + "font_style" => "normal", + "text_decoration" => "none", + "text_align" => null, + "stroke" => "none", + ); + + private static function text_style($layer, $layer_parameter) + { + $font_family = Cloudinary::option_get($layer, "font_family"); + $font_size = Cloudinary::option_get($layer, "font_size"); + $keywords = array(); + + foreach (Cloudinary::$LAYER_KEYWORD_PARAMS as $attr => $default_value) { + $attr_value = Cloudinary::option_get($layer, $attr, $default_value); + if ($attr_value != $default_value) { + array_push($keywords, $attr_value); + } + } + + $letter_spacing = Cloudinary::option_get($layer, "letter_spacing"); + if ($letter_spacing != null) { + array_push($keywords, "letter_spacing_$letter_spacing"); + } + + $line_spacing = Cloudinary::option_get($layer, "line_spacing"); + if ($line_spacing != null) { + array_push($keywords, "line_spacing_$line_spacing"); + } + + $font_antialiasing = Cloudinary::option_get($layer, "font_antialiasing"); + if ($font_antialiasing != null) { + array_push($keywords, "antialias_$font_antialiasing"); + } + + $font_hinting = Cloudinary::option_get($layer, "font_hinting"); + if ($font_hinting != null) { + array_push($keywords, "hinting_$font_hinting"); + } + + $has_text_options = $font_size != null || $font_family != null || !empty($keywords); + if (!$has_text_options) { + return null; + } + if ($font_family == null) { + throw new InvalidArgumentException("Must supply font_family for text in $layer_parameter"); + } + if ($font_size == null) { + throw new InvalidArgumentException("Must supply font_size for text in $layer_parameter"); + } + array_unshift($keywords, $font_size); + array_unshift($keywords, $font_family); + + return implode("_", array_filter($keywords, 'Cloudinary::is_not_null')); + } + + + /** + * Handle overlays. + * Overlay properties can came as array or as string. + * @param $layer + * @param $layer_parameter + * @return string + */ + private static function process_layer($layer, $layer_parameter) + { + // When overlay is array. + if (is_array($layer)) { + $resource_type = Cloudinary::option_get($layer, "resource_type"); + $type = Cloudinary::option_get($layer, "type"); + $text = Cloudinary::option_get($layer, "text"); + $fetch = Cloudinary::option_get($layer, "fetch"); + $text_style = null; + $public_id = Cloudinary::option_get($layer, "public_id"); + $format = Cloudinary::option_get($layer, "format"); + $components = array(); + + if ($public_id != null) { + $public_id = str_replace("/", ":", $public_id); + if ($format != null) { + $public_id = $public_id . "." . $format; + } + } + + // Fetch overlay. + if (!empty($fetch) || $resource_type === "fetch") { + $public_id = null; + $resource_type = "fetch"; + $fetch = self::base64url_encode($fetch); + } // Text overlay. + elseif (!empty($text) || $resource_type === "text") { + $resource_type = "text"; + $type = null; // type is ignored for text layers + $text_style = Cloudinary::text_style($layer, $layer_parameter); #FIXME duplicate + if ($text != null) { + if (!($public_id != null xor $text_style != null)) { + throw new InvalidArgumentException( + "Must supply either style parameters or a public_id when providing text parameter in a text $layer_parameter" + ); + } + $escaped = Cloudinary::smart_escape($text); + $escaped = str_replace("%2C", "%252C", $escaped); + $escaped = str_replace("/", "%252F", $escaped); + # Don't encode interpolation expressions e.g. $(variable) + preg_match_all('/\$\([a-zA-Z]\w+\)/', $text, $matches); + foreach ($matches[0] as $match) { + $escaped_match = Cloudinary::smart_escape($match); + $escaped = str_replace($escaped_match, $match, $escaped); + } + + $text = $escaped; + } + } else { + if ($public_id == null) { + throw new InvalidArgumentException("Must supply public_id for $resource_type $layer_parameter"); + } + if ($resource_type == "subtitles") { + $text_style = Cloudinary::text_style($layer, $layer_parameter); + } + } + + // Build a components array. + if ($resource_type != "image") { + array_push($components, $resource_type); + } + if ($type != "upload") { + array_push($components, $type); + } + array_push($components, $text_style); + array_push($components, $public_id); + array_push($components, $text); + array_push($components, $fetch); + + // Build a valid overlay string. + $layer = implode(":", array_filter($components, 'Cloudinary::is_not_null')); + } // Handle fetch overlay from string definition. + elseif (substr($layer, 0, strlen('fetch:')) === 'fetch:') { + $url = substr($layer, strlen('fetch:')); + $b64 = self::base64url_encode($url); + $layer = 'fetch:' . $b64; + } + + return $layer; + } + + private static $CONDITIONAL_OPERATORS = array( + "=" => 'eq', + "!=" => 'ne', + "<" => 'lt', + ">" => 'gt', + "<=" => 'lte', + ">=" => 'gte', + "&&" => 'and', + "||" => 'or', + "*" => 'mul', + "/" => 'div', + "+" => 'add', + "-" => 'sub', + "^" => 'pow', + ); + private static $PREDEFINED_VARS = array( + "aspect_ratio" => "ar", + "current_page" => "cp", + "duration" => "du", + "face_count" => "fc", + "height" => "h", + "initial_aspect_ratio" => "iar", + "initial_duration" => "idu", + "initial_height" => "ih", + "initial_width" => "iw", + "page_count" => "pc", + "page_x" => "px", + "page_y" => "py", + "tags" => "tags", + "width" => "w", + ); + + private static function translate_if($source) + { + if (isset(self::$CONDITIONAL_OPERATORS[$source[0]])) { + return self::$CONDITIONAL_OPERATORS[$source[0]]; + } elseif (isset(self::$PREDEFINED_VARS[$source[0]])) { + return self::$PREDEFINED_VARS[$source[0]]; + } else { + return $source[0]; + } + } + + private static $IF_REPLACE_RE; + + private static function process_if($if) + { + $if = self::normalize_expression($if); + + return $if; + } + + private static function float_to_string($value) { + if (!is_float($value)) { + return $value; + } + + $locale = localeconv(); + $string = strval($value); + $string = str_replace($locale['decimal_point'], '.', $string); + + return $string; + } + + private static function normalize_expression($exp) + { + if (is_float($exp)) { + return self::float_to_string($exp); + } + if (preg_match('/^!.+!$/', $exp)) { + return $exp; + } else { + if (empty(self::$IF_REPLACE_RE)) { + self::$IF_REPLACE_RE = '/((\|\||>=|<=|&&|!=|>|=|<|\/|\-|\+|\*|\^)(?=[ _])|(?= 2) { + return array($range[0], end($range)); + } else { + if (is_string($range) && preg_match(Cloudinary::RANGE_RE, $range) == 1) { + return explode("..", $range, 2); + } else { + return null; + } + } + } + + private static function norm_range_value($value) + { + if (is_null($value)) { + return null; + } + + // Ensure that trailing decimal(.0) part is not cropped when float is provided + // e.g. float 1.0 should be returned as "1.0" and not "1" as it happens by default + if (is_float($value) && $value - (int)$value == 0) { + $value = sprintf("%.1f", $value); + } + + preg_match(Cloudinary::RANGE_VALUE_RE, $value, $matches); + + if (empty($matches)) { + return null; + } + + $modifier = ''; + if (!empty($matches['modifier'])) { + $modifier = 'p'; + } + + return $matches['value'] . $modifier; + } + + private static function norm_auto_range_value($value) + { + if ($value == 'auto') { + return $value; + } + return self::norm_range_value($value); + } + + private static function process_video_codec_param($param) + { + $out_param = $param; + if (is_array($out_param)) { + $out_param = $param['codec']; + if (array_key_exists('profile', $param)) { + $out_param = $out_param . ':' . $param['profile']; + if (array_key_exists('level', $param)) { + $out_param = $out_param . ':' . $param['level']; + } + } + } + + return $out_param; + } + + /** + * Serializes fps transformation parameter + * + * @param mixed $fps A single number, an array of mixed type, a string, including open-ended and closed range values + * Examples: '24-29.97', 24, 24.973, '-24', [24, 29.97] + * + * @return string + */ + private static function process_fps($fps) + { + if (!is_array($fps)) { + return strval($fps); + } + + return implode("-", array_map("self::normalize_expression", $fps)); + } + + /** + * Serializes keyframe_interval transformation parameter + * + * @param float|int|string $keyframe_interval A positive number or a string + * + * @return string + */ + private static function process_keyframe_interval($keyframe_interval) + { + if (is_string($keyframe_interval) || $keyframe_interval == null) { + return $keyframe_interval; + } + if (!is_numeric($keyframe_interval)) { + throw new InvalidArgumentException("Keyframe interval should be a number or a string"); + } + if ($keyframe_interval < 0) { + throw new InvalidArgumentException("Keyframe interval should be greater than zero"); + } + if (is_int($keyframe_interval)) { + return $keyframe_interval . ".0"; + } + return $keyframe_interval; + } + + // Warning: $options are being destructively updated! + public static function cloudinary_url($source, &$options = array()) + { + $source = self::check_cloudinary_field($source, $options); + self::patch_fetch_format($options); + $type = Cloudinary::option_consume($options, "type", "upload"); + $transformation = Cloudinary::generate_transformation_string($options); + + $resource_type = Cloudinary::option_consume($options, "resource_type", "image"); + $version = Cloudinary::option_consume($options, "version"); + $force_version = Cloudinary::option_consume( + $options, + "force_version", + Cloudinary::config_get("force_version", true) + ); + $format = Cloudinary::option_consume($options, "format"); + + $cloud_name = Cloudinary::option_consume($options, "cloud_name", Cloudinary::config_get("cloud_name")); + if (!$cloud_name) { + throw new InvalidArgumentException("Must supply cloud_name in tag or in configuration"); + } + $secure = Cloudinary::option_consume($options, "secure", Cloudinary::config_get("secure")); + $private_cdn = Cloudinary::option_consume($options, "private_cdn", Cloudinary::config_get("private_cdn")); + $secure_distribution = Cloudinary::option_consume( + $options, + "secure_distribution", + Cloudinary::config_get("secure_distribution") + ); + $cdn_subdomain = Cloudinary::option_consume( + $options, + "cdn_subdomain", + Cloudinary::config_get("cdn_subdomain") + ); + $secure_cdn_subdomain = Cloudinary::option_consume( + $options, + "secure_cdn_subdomain", + Cloudinary::config_get("secure_cdn_subdomain") + ); + $cname = Cloudinary::option_consume($options, "cname", Cloudinary::config_get("cname")); + $shorten = Cloudinary::option_consume($options, "shorten", Cloudinary::config_get("shorten")); + $sign_url = Cloudinary::option_consume($options, "sign_url", Cloudinary::config_get("sign_url")); + $api_secret = Cloudinary::option_consume($options, "api_secret", Cloudinary::config_get("api_secret")); + $url_suffix = Cloudinary::option_consume($options, "url_suffix", Cloudinary::config_get("url_suffix")); + $use_root_path = Cloudinary::option_consume($options, "use_root_path", Cloudinary::config_get("use_root_path")); + $auth_token = Cloudinary::option_consume($options, "auth_token"); + if (is_array($auth_token)) { + $auth_token = array_merge(self::config_get("auth_token", array()), $auth_token); + } elseif (is_null($auth_token)) { + $auth_token = self::config_get("auth_token"); + } + + if (!$source) { + return $source; + } + + if (preg_match("/^https?:\//i", $source)) { + if ($type == "upload") { + return $source; + } + } + + $resource_type_and_type = Cloudinary::finalize_resource_type( + $resource_type, + $type, + $url_suffix, + $use_root_path, + $shorten + ); + $sources = Cloudinary::finalize_source($source, $format, $url_suffix); + $source = $sources["source"]; + $source_to_sign = $sources["source_to_sign"]; + + if (empty($version) && $force_version && strpos($source_to_sign, "/") && + !preg_match("/^https?:\//", $source_to_sign) && !preg_match("/^v[0-9]+/", $source_to_sign)) { + $version = "1"; + } + $version = $version ? "v" . $version : null; + + $signature = null; + if ($sign_url && !$auth_token) { + $to_sign = implode("/", array_filter(array($transformation, $source_to_sign))); + $signature = self::base64url_encode(sha1($to_sign . $api_secret, true)); + $signature = 's--' . substr($signature, 0, 8) . '--'; + } + + $prefix = Cloudinary::unsigned_download_url_prefix( + $source, + $cloud_name, + $private_cdn, + $cdn_subdomain, + $secure_cdn_subdomain, + $cname, + $secure, + $secure_distribution + ); + + $source = preg_replace( + "/([^:])\/+/", + "$1/", + implode( + "/", + array_filter( + array( + $prefix, + $resource_type_and_type, + $signature, + $transformation, + $version, + $source, + ) + ) + ) + ); + + if ($sign_url && $auth_token) { + $path = parse_url($source, PHP_URL_PATH); + $token = \Cloudinary\AuthToken::generate(array_merge($auth_token, array("url" => $path))); + $source = $source . "?" . $token; + } + + return $source; + } + + private static function finalize_source($source, $format, $url_suffix) + { + $source = preg_replace('/([^:])\/\//', '$1/', $source); + if (preg_match('/^https?:\//i', $source)) { + $source = Cloudinary::smart_escape($source); + $source_to_sign = $source; + } else { + $source = Cloudinary::smart_escape(rawurldecode($source)); + $source_to_sign = $source; + if (!empty($url_suffix)) { + if (preg_match('/[\.\/]/i', $url_suffix)) { + throw new InvalidArgumentException("url_suffix should not include . or /"); + } + $source = $source . '/' . $url_suffix; + } + if (!empty($format)) { + $source = $source . '.' . $format; + $source_to_sign = $source_to_sign . '.' . $format; + } + } + + return array("source" => $source, "source_to_sign" => $source_to_sign); + } + + private static function finalize_resource_type($resource_type, $type, $url_suffix, $use_root_path, $shorten) + { + if (empty($type)) { + $type = "upload"; + } + + if (!empty($url_suffix)) { + if ($resource_type == "image" && $type == "upload") { + $resource_type = "images"; + $type = null; + } elseif ($resource_type == "image" && $type == "private") { + $resource_type = "private_images"; + $type = null; + } elseif ($resource_type == "image" && $type == "authenticated") { + $resource_type = "authenticated_images"; + $type = null; + } elseif ($resource_type == "video" && $type == "upload") { + $resource_type = "videos"; + $type = null; + } elseif ($resource_type == "raw" && $type == "upload") { + $resource_type = "files"; + $type = null; + } else { + throw new InvalidArgumentException( + "URL Suffix only supported for image/upload, image/private, image/authenticated, " . + "video/upload and raw/upload" + ); + } + } + + if ($use_root_path) { + if (($resource_type == "image" && $type == "upload") || ($resource_type == "images" && empty($type))) { + $resource_type = null; + $type = null; + } else { + throw new InvalidArgumentException("Root path only supported for image/upload"); + } + } + if ($shorten && $resource_type == "image" && $type == "upload") { + $resource_type = "iu"; + $type = null; + } + $out = ""; + if (!empty($resource_type)) { + $out = $resource_type; + } + if (!empty($type)) { + $out = $out.'/'.$type; + } + + return $out; + } + + // cdn_subdomain and secure_cdn_subdomain + // 1) Customers in shared distribution (e.g. res.cloudinary.com) + // if cdn_domain is true uses res-[1-5].cloudinary.com for both http and https. Setting secure_cdn_subdomain to false disables this for https. + // 2) Customers with private cdn + // if cdn_domain is true uses cloudname-res-[1-5].cloudinary.com for http + // if secure_cdn_domain is true uses cloudname-res-[1-5].cloudinary.com for https (please contact support if you require this) + // 3) Customers with cname + // if cdn_domain is true uses a[1-5].cname for http. For https, uses the same naming scheme as 1 for shared distribution and as 2 for private distribution. + private static function unsigned_download_url_prefix( + $source, + $cloud_name, + $private_cdn, + $cdn_subdomain, + $secure_cdn_subdomain, + $cname, + $secure, + $secure_distribution + ) { + $shared_domain = !$private_cdn; + $prefix = null; + if ($secure) { + if (empty($secure_distribution) || $secure_distribution == Cloudinary::OLD_AKAMAI_SHARED_CDN) { + $secure_distribution = $private_cdn ? $cloud_name . '-res.cloudinary.com' : Cloudinary::SHARED_CDN; + } + + if (empty($shared_domain)) { + $shared_domain = ($secure_distribution == Cloudinary::SHARED_CDN); + } + + if (is_null($secure_cdn_subdomain) && $shared_domain) { + $secure_cdn_subdomain = $cdn_subdomain; + } + + if ($secure_cdn_subdomain) { + $secure_distribution = str_replace( + 'res.cloudinary.com', + "res-" . Cloudinary::domain_shard($source) . ".cloudinary.com", + $secure_distribution + ); + } + + $prefix = "https://".$secure_distribution; + } else { + if ($cname) { + $subdomain = $cdn_subdomain ? "a" . Cloudinary::domain_shard($source) . '.' : ""; + $prefix = "http://" . $subdomain . $cname; + } else { + $host = implode( + array( + $private_cdn ? $cloud_name."-" : "", + "res", + $cdn_subdomain ? "-".Cloudinary::domain_shard($source) : "", + ".cloudinary.com", + ) + ); + $prefix = "http://".$host; + } + } + if ($shared_domain) { + $prefix = $prefix . '/' . $cloud_name; + } + + return $prefix; + } + + private static function domain_shard($source) + { + return (((crc32($source) % 5) + 5) % 5 + 1); + } + + // Warning: $options are being destructively updated! + public static function check_cloudinary_field($source, &$options = array()) + { + // [/][/][v/][.][#] + $IDENTIFIER_RE = "~" . + "^" . + "(?:([^/]+)/)??" . // resource type + "(?:([^/]+)/)??" . // type + "(?:(?:v(\\d+)/)(?:([^#]+)/)?)?" . // version + "([^#/]+?)" . // public ID + "(?:\\.([^.#/]+))?" . //format + "(?:#([^/]+))?" . // signature + "$" . + "~"; + if (!is_object($source) || !method_exists($source, 'identifier')) { + // $source doesn't look like a CloudinaryField, so just return it + return $source; + } + + // $source is a CloudinaryField, parse its identifier + $matches = array(); + $identifier = $source->identifier(); + if (!$identifier || strstr(':', $identifier) !== false || !preg_match($IDENTIFIER_RE, $identifier, $matches)) { + return $source; + } + $optionNames = array('resource_type', 'type', 'version', 'folder', 'public_id', 'format'); + foreach ($optionNames as $index => $optionName) { + if (@$matches[$index + 1]) { + $options[$optionName] = $matches[$index + 1]; + } + } + + return Cloudinary::option_consume($options, 'public_id'); + } + + // Based on http://stackoverflow.com/a/1734255/526985 + private static function smart_escape($str) + { + $revert = array('%3A' => ':', '%2F' => '/'); + + return strtr(rawurlencode($str), $revert); + } + + public static function cloudinary_api_url($action = 'upload', $options = array()) + { + $cloudinary = Cloudinary::option_get( + $options, + "upload_prefix", + Cloudinary::config_get("upload_prefix", "https://api.cloudinary.com") + ); + $cloud_name = Cloudinary::option_get($options, "cloud_name", Cloudinary::config_get("cloud_name")); + if (!$cloud_name) { + throw new InvalidArgumentException("Must supply cloud_name in options or in configuration"); + } + $resource_type = Cloudinary::option_get($options, "resource_type", "image"); + + return implode("/", array($cloudinary, "v1_1", $cloud_name, $resource_type, $action)); + } + + public static function random_public_id() + { + return substr(sha1(uniqid(Cloudinary::config_get("api_secret", "") . mt_rand())), 0, 16); + } + + public static function signed_preloaded_image($result) + { + return $result["resource_type"] . "/upload/v" . $result["version"] . "/" . $result["public_id"] . + (isset($result["format"]) ? "." . $result["format"] : "") . "#" . $result["signature"]; + } + + /** + * Generates a cloudinary url scaled to specified width. + * + * In case transformation parameter is provided, it is used instead of transformations specified in $options + * + * @param string $source Public ID of the resource + * @param int $width Width in pixels of the srcset item + * @param array|string $transformation Custom transformation that overrides transformations provided in $options + * @param array $options Additional options + * + * @return null|string + */ + public static function cloudinary_scaled_url($source, $width, $transformation, $options) + { + if (!empty($transformation)) { + // Replace transformation parameters in $options with those in $transformation + + if(is_string($transformation)){ + $transformation = array("raw_transformation"=> $transformation); + } + $options = self::array_subset($options, self::$URL_KEYS); + $options = array_merge($options, $transformation); + } + + $scale_transformation = ["crop" => "scale", "width" => $width]; + + self::check_cloudinary_field($source, $options); + self::patch_fetch_format($options); + $options = self::chain_transformations($options, $scale_transformation); + + return cloudinary_url_internal($source, $options); + } + + # Utility method that uses the deprecated ZIP download API. + # @deprecated Replaced by {download_zip_url} that uses the more advanced and robust archive generation and download API + public static function zip_download_url($tag, $options = array()) + { + $params = array( + "timestamp" => time(), + "tag" => $tag, + "transformation" => \Cloudinary::generate_transformation_string($options), + ); + $params = Cloudinary::sign_request($params, $options); + return Cloudinary::cloudinary_api_url("download_tag.zip", $options) . "?" . http_build_query($params); + } + + + # Returns a URL that when invokes creates an archive and returns it. + # @param options [Hash] + # @option options [String] resource_type The resource type of files to include in the archive. Must be one of image | video | raw + # @option options [String] type (upload) The specific file type of resources upload|private|authenticated + # @option options [String|Array] tags (nil) list of tags to include in the archive + # @option options [String|Array] public_ids (nil) list of public_ids to include in the archive + # @option options [String|Array] prefixes (nil) Optional list of prefixes of public IDs (e.g., folders). + # @option options [String|Array] transformations Optional list of transformations. + # The derived images of the given transformations are included in the archive. Using the string representation of + # multiple chained transformations as we use for the 'eager' upload parameter. + # @option options [String] mode (create) return the generated archive file or to store it as a raw resource and + # return a JSON with URLs for accessing the archive. Possible values download, create + # @option options [String] target_format (zip) + # @option options [String] target_public_id Optional public ID of the generated raw resource. + # Relevant only for the create mode. If not specified, random public ID is generated. + # @option options [boolean] flatten_folders (false) If true, flatten public IDs with folders to be in the root of the archive. + # Add numeric counter to the file name in case of a name conflict. + # @option options [boolean] flatten_transformations (false) If true, and multiple transformations are given, + # flatten the folder structure of derived images and store the transformation details on the file name instead. + # @option options [boolean] use_original_filename Use the original file name of included images (if available) instead of the public ID. + # @option options [boolean] async (false) If true, return immediately and perform the archive creation in the background. + # Relevant only for the create mode. + # @option options [String] notification_url Optional URL to send an HTTP post request (webhook) when the archive creation is completed. + # @option options [String|Array \Cloudinary::option_get($options, "allow_missing"), + "async" => \Cloudinary::option_get($options, "async"), + "expires_at" => \Cloudinary::option_get($options, "expires_at"), + "flatten_folders" => \Cloudinary::option_get($options, "flatten_folders"), + "flatten_transformations" => \Cloudinary::option_get($options, "flatten_transformations"), + "keep_derived" => \Cloudinary::option_get($options, "keep_derived"), + "mode" => \Cloudinary::option_get($options, "mode"), + "notification_url" => \Cloudinary::option_get($options, "notification_url"), + "phash" => \Cloudinary::option_get($options, "phash"), + "prefixes" => \Cloudinary::build_array(\Cloudinary::option_get($options, "prefixes")), + "public_ids" => \Cloudinary::build_array(\Cloudinary::option_get($options, "public_ids")), + "fully_qualified_public_ids" => \Cloudinary::build_array( + \Cloudinary::option_get($options, "fully_qualified_public_ids") + ), + "skip_transformation_name" => \Cloudinary::option_get($options, "skip_transformation_name"), + "tags" => \Cloudinary::build_array(\Cloudinary::option_get($options, "tags")), + "target_format" => \Cloudinary::option_get($options, "target_format"), + "target_public_id" => \Cloudinary::option_get($options, "target_public_id"), + "target_tags" => \Cloudinary::build_array(\Cloudinary::option_get($options, "target_tags")), + "timestamp" => time(), + "transformations" => \Cloudinary::build_eager(\Cloudinary::option_get($options, "transformations")), + "type" => \Cloudinary::option_get($options, "type"), + "use_original_filename" => \Cloudinary::option_get($options, "use_original_filename"), + ); + array_walk( + $params, + function (&$value, $key) { + $value = (is_bool($value) ? ($value ? "1" : "0") : $value); + } + ); + + return array_filter( + $params, + function ($v) { + return !is_null($v) && ($v !== ""); + } + ); + } + + public static function build_eager($transformations) + { + $eager = array(); + foreach (\Cloudinary::build_array($transformations) as $trans) { + $single_eager = \Cloudinary::build_single_eager($trans); + array_push($eager, $single_eager); + } + + return implode("|", $eager); + } + + /** + * Builds a single eager transformation which consists of transformation and (optionally) format joined by "/" + * + * @param array|string $options Options containing transformation parameters and (optionally) a "format" key + * format can be a string value (jpg, gif, etc) or can be set to "" (empty string). + * The latter leads to transformation ending with "/", which means "No extension, use original format" + * If format is not provided or set to null, only transformation is used (without the trailing "/") + * + * @return string + */ + public static function build_single_eager($options) + { + if (is_string($options)) { + return $options; + } + + $trans_str = \Cloudinary::generate_transformation_string($options); + + if (empty($trans_str)) { + return ""; + } + + $file_format_str = ""; + if (isset($options["format"])) { + $file_format_str = "/" . $options["format"]; + } + + return $trans_str . $file_format_str; + } + + public static function private_download_url($public_id, $format, $options = array()) + { + $cloudinary_params = Cloudinary::sign_request( + array( + "timestamp" => time(), + "public_id" => $public_id, + "format" => $format, + "type" => Cloudinary::option_get($options, "type"), + "attachment" => Cloudinary::option_get($options, "attachment"), + "expires_at" => Cloudinary::option_get($options, "expires_at"), + ), + $options + ); + + return Cloudinary::cloudinary_api_url("download", $options) . "?" . http_build_query($cloudinary_params); + } + + public static function sign_request($params, &$options) + { + $api_key = Cloudinary::option_get($options, "api_key", Cloudinary::config_get("api_key")); + if (!$api_key) { + throw new \InvalidArgumentException("Must supply api_key"); + } + $api_secret = Cloudinary::option_get($options, "api_secret", Cloudinary::config_get("api_secret")); + if (!$api_secret) { + throw new \InvalidArgumentException("Must supply api_secret"); + } + + # Remove blank parameters + $params = array_filter( + $params, + function ($v) { + return isset($v) && $v !== ""; + } + ); + + $params["signature"] = Cloudinary::api_sign_request($params, $api_secret); + $params["api_key"] = $api_key; + + return $params; + } + + public static function api_sign_request($params_to_sign, $api_secret) + { + $params = array(); + foreach ($params_to_sign as $param => $value) { + if (isset($value) && $value !== "") { + if (!is_array($value)) { + $params[$param] = $value; + } else { + if (count($value) > 0) { + $params[$param] = implode(",", $value); + } + } + } + } + ksort($params); + $join_pair = function ($key, $value) { + return $key . "=" . $value; + }; + $to_sign = implode("&", array_map($join_pair, array_keys($params), array_values($params))); + return sha1($to_sign . $api_secret); + } + + public static function html_attrs($options, $only = null) + { + $attrs = array(); + foreach ($options as $k => $v) { + $key = $k; + $value = $v; + if (is_int($k)) { + $key = $v; + $value = ""; + } + if (is_array($only) && array_search($key, $only) !== false || !is_array($only)) { + $attrs[$key] = $value; + } + } + ksort($attrs); + + $join_pair = function ($key, $value) { + $out = $key; + if (!empty($value)) { + $out .= '=\'' . htmlspecialchars($value, ENT_QUOTES) . '\''; + } + + return $out; + }; + + return implode(" ", array_map($join_pair, array_keys($attrs), array_values($attrs))); + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/CloudinaryField.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/CloudinaryField.php new file mode 100644 index 0000000..d247fab --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/CloudinaryField.php @@ -0,0 +1,63 @@ +identifier = $identifier; + } + + public function __toString() + { + return (string)explode('#', $this->identifier())[0]; + } + + public function identifier() + { + return $this->identifier; + } + + public function url($options = array()) + { + if (!$this->identifier) { + // TODO: Error? + return null; + } + + return cloudinary_url($this, $options); + } + + public function upload($file, $options = array()) + { + $options['return_error'] = false; + $ret = Uploader::upload($file, $options); + $preloaded = new PreloadedFile(\Cloudinary::signed_preloaded_image($ret)); + $this->identifier = $preloaded->extended_identifier(); + } + + public function delete() + { + $options['return_error'] = false; + Uploader::destroy($this->identifier); + unset($this->identifier); + } + + public function verify() + { + $preloaded = new PreloadedFile($this->identifier); + return $preloaded->is_valid(); + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Error.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Error.php new file mode 100644 index 0000000..e0b0461 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Error.php @@ -0,0 +1,11 @@ + "mp4", + "codecs" => "hev1", + "transformations" => ["video_codec" => "h265"] + ], + [ + "type" => "webm", + "codecs" => "vp9", + "transformations" => ["video_codec" => "vp9"] + ], + [ + "type" => "mp4", + "transformations" => ["video_codec" => "auto"] + ], + [ + "type" => "webm", + "transformations" => ["video_codec" => "auto"] + ], + ]; + } + + function cl_upload_url($options = array()) + { + $options['resource_type'] = Cloudinary::option_get($options, 'resource_type', 'auto'); + $endpoint = array_key_exists('chunk_size', $options) ? 'upload_chunked' : 'upload'; + + return Cloudinary::cloudinary_api_url($endpoint, $options); + } + + function cl_upload_tag_params($options = array()) + { + $params = Cloudinary\Uploader::build_upload_params($options); + if (Cloudinary::option_get($options, "unsigned")) { + $params = array_filter( + $params, + function ($v) { + return !is_null($v) && ($v !== ""); + } + ); + } else { + $params = Cloudinary::sign_request($params, $options); + } + + return json_encode($params); + } + + function cl_unsigned_image_upload_tag($field, $upload_preset, $options = array()) + { + return cl_image_upload_tag( + $field, + array_merge($options, array("unsigned" => true, "upload_preset" => $upload_preset)) + ); + } + + function cl_image_upload_tag($field, $options = array()) + { + return cl_upload_tag($field, $options); + } + + function cl_upload_tag($field, $options = array()) + { + $html_options = Cloudinary::option_get($options, "html", array()); + + $classes = array("cloudinary-fileupload"); + if (isset($html_options["class"])) { + array_unshift($classes, Cloudinary::option_consume($html_options, "class")); + } + $tag_options = array_merge( + $html_options, + array( + "type" => "file", + "name" => "file", + "data-url" => cl_upload_url($options), + "data-form-data" => cl_upload_tag_params($options), + "data-cloudinary-field" => $field, + "class" => implode(" ", $classes), + ) + ); + if (array_key_exists('chunk_size', $options)) { + $tag_options['data-max-chunk-size'] = $options['chunk_size']; + } + + return ''; + } + + function cl_form_tag($callback_url, $options = array()) + { + $form_options = Cloudinary::option_get($options, "form", array()); + + $options["callback_url"] = $callback_url; + + $params = Cloudinary\Uploader::build_upload_params($options); + $params = Cloudinary::sign_request($params, $options); + + $api_url = Cloudinary::cloudinary_api_url("upload", $options); + + $form = "
\n"; + foreach ($params as $key => $value) { + $attributes = array( + "name" => $key, + "value" => $value, + "type" => "hidden", + ); + $form .= "\n"; + } + $form .= "
\n"; + + return $form; + } + + /** + * Generates an HTML meta tag that enables Client-Hints + * + * @return string Resulting meta tag + */ + function cl_client_hints_meta_tag() + { + return ""; + } + /** + * @internal + * Helper function. Validates src_data parameters + * + * @param array $srcset_data { + * + * @var array breakpoints An array of breakpoints. + * @var int min_width Minimal width of the srcset images. + * @var int max_width Maximal width of the srcset images. + * @var int max_images Number of srcset images to generate. + * } + * + * @return bool true on success or false on failure + */ + function validate_srcset_data($srcset_data) + { + foreach (array('min_width', 'max_width', 'max_images') as $arg) { + if (empty($srcset_data[$arg]) || !is_numeric($srcset_data[$arg]) || is_string($srcset_data[$arg])) { + error_log('Either valid (min_width, max_width, max_images) or breakpoints must be provided ' . + 'to the image srcset attribute'); + return false; + } + } + + if ($srcset_data['min_width'] > $srcset_data['max_width']) { + error_log('min_width must be less than max_width'); + return false; + } + + if ($srcset_data['max_images'] <= 0) { + error_log('max_images must be a positive integer'); + return false; + } + + return true; + } + + /** + * @internal + * Helper function. Calculates static srcset breakpoints using provided parameters + * + * Either the breakpoints or min_width, max_width, max_images must be provided. + * + * @param array $srcset_data { + * + * @var array breakpoints An array of breakpoints. + * @var int min_width Minimal width of the srcset images. + * @var int max_width Maximal width of the srcset images. + * @var int max_images Number of srcset images to generate. + * } + * + * @return array Array of breakpoints + * + * @throws InvalidArgumentException In case of invalid or missing parameters + */ + function generate_breakpoints($srcset_data) + { + $breakpoints = Cloudinary::option_get($srcset_data, "breakpoints", array()); + + if (!empty($breakpoints)) { + return $breakpoints; + } + + if (!validate_srcset_data($srcset_data)) { + return null; + } + + $min_width = $srcset_data['min_width']; + $max_width = $srcset_data['max_width']; + $max_images = $srcset_data['max_images']; + + if ($max_images == 1) { + // if user requested only 1 image in srcset, we return max_width one + $min_width = $max_width; + } + + $step_size = (int)ceil(($max_width - $min_width) / ($max_images > 1 ? $max_images - 1 : 1)); + + $curr_breakpoint = $min_width; + + while ($curr_breakpoint < $max_width) { + array_push($breakpoints, $curr_breakpoint); + $curr_breakpoint += $step_size; + } + + array_push($breakpoints, $max_width); + + return $breakpoints; + } + + /** + * @internal + * Helper function. Retrieves responsive breakpoints list from cloudinary server + * + * When passing special string to transformation `width` parameter of form `auto:breakpoints{parameters}:json`, + * the response contains JSON with data of the responsive breakpoints + * + * @param string $public_id The public ID of the image + * @param array $srcset_data { + * + * @var int min_width Minimal width of the srcset images + * @var int max_width Maximal width of the srcset images + * @var int bytes_step Minimal bytes step between images + * @var int max_images Number of srcset images to generate + * } + * @param array $options Cloudinary url options + * + * @return array Resulting breakpoints + * + * @throws \Cloudinary\Error + */ + function fetch_breakpoints($public_id, $srcset_data = array(), $options = array()) + { + $min_width = \Cloudinary::option_get($srcset_data, 'min_width', 50); + $max_width = \Cloudinary::option_get($srcset_data, 'max_width', 1000); + $bytes_step = \Cloudinary::option_get($srcset_data, 'bytes_step', 20000); + $max_images = \Cloudinary::option_get($srcset_data, 'max_images', 20); + $transformation = \Cloudinary::option_get($srcset_data, 'transformation'); + + $kbytes_step = (int)ceil($bytes_step / 1024); + + $width_param = "auto:breakpoints_${min_width}_${max_width}_${kbytes_step}_${max_images}:json"; + // We use Cloudinary::cloudinary_scaled_url function, passing special `width` parameter + $breakpoints_url = Cloudinary::cloudinary_scaled_url($public_id, $width_param, $transformation, $options); + + $client = new HttpClient(); + + return $client->getJSON($breakpoints_url)["breakpoints"]; + } + + /** + * @internal + * Helper function. Gets from cache or calculates srcset breakpoints using provided parameters + * + * @param string $public_id Public ID of the resource + * @param array $srcset_data { + * + * @var array breakpoints An array of breakpoints. + * @var int min_width Minimal width of the srcset images. + * @var int max_width Maximal width of the srcset images. + * @var int max_images Number of srcset images to generate. + * } + * + * @param array $options Additional options + * + * @return array|null Array of breakpoints, null if failed + */ + function get_or_generate_breakpoints($public_id, $srcset_data, $options = array()) + { + $breakpoints = Cloudinary::option_get($srcset_data, "breakpoints", null); + + if (!empty($breakpoints)) { + # User might provide explicit breakpoints, in this case we omit calculation and cache + return $breakpoints; + } + + if (Cloudinary::option_get($srcset_data, "use_cache", false)) { + $breakpoints = ResponsiveBreakpointsCache::instance()->get($public_id, $options); + + if (is_null($breakpoints)) { + // Cache miss, let's bring breakpoints from Cloudinary + try { + $breakpoints = fetch_breakpoints($public_id, $srcset_data, $options); + } catch (\Cloudinary\Error $e) { + error_log("Failed getting responsive breakpoints: $e"); + } + + if (!is_null($breakpoints)) { + ResponsiveBreakpointsCache::instance()->set($public_id, $options, $breakpoints); + } + } + } + + if (empty($breakpoints)) { + // Static calculation if cache is not enabled or we failed to fetch breakpoints + $breakpoints = generate_breakpoints($srcset_data); + } + + return $breakpoints; + } + + /** + * @internal + * Helper function. Generates srcset attribute value of the HTML img tag + * + * @param array $srcset_data { + * + * @var array breakpoints An array of breakpoints. + * @var int min_width Minimal width of the srcset images. + * @var int max_width Maximal width of the srcset images. + * @var int max_images Number of srcset images to generate. + * } + * + * @param array $options Additional options. + * + * @return string Resulting srcset attribute value + * + * @throws InvalidArgumentException In case of invalid or missing parameters + */ + function generate_srcset_attribute($public_id, $breakpoints, $transformation = null, $options = array()) + { + if (empty($breakpoints)) { + return null; + } + + $items = array(); + foreach ($breakpoints as $breakpoint) { + array_push( + $items, + Cloudinary::cloudinary_scaled_url($public_id, $breakpoint, $transformation, $options) . " {$breakpoint}w" + ); + } + + return implode(", ", $items); + } + + /** + * @internal + * Helper function. Generates a sizes attribute for HTML tags + * + * @var array breakpoints An array of breakpoints. + * + * @return string Resulting sizes attribute value + * + */ + function generate_sizes_attribute($breakpoints) + { + if (empty($breakpoints)) { + return null; + } + + $sizes_items = array(); + foreach ($breakpoints as $breakpoint) { + array_push($sizes_items, "(max-width: {$breakpoint}px) {$breakpoint}px"); + } + + return implode(", ", $sizes_items); + } + + /** + * @internal + * Helper function. Generates srcset and sizes attributes of the image tag + * + * Generated attributes are added to $attributes argument + * + * @param string $public_id The public ID of the resource + * @param array $attributes Existing attributes + * @param array $srcset_data { + * + * @var array breakpoints An array of breakpoints. + * @var int min_width Minimal width of the srcset images. + * @var int max_width Maximal width of the srcset images. + * @var int max_images Number of srcset images to generate. + * } + * + * @param array $options Additional options. + * + * @return array The responsive attributes + */ + function generate_image_responsive_attributes($public_id, $attributes, $srcset_data, $options) + { + // Create both srcset and sizes here to avoid fetching breakpoints twice + + $responsive_attributes = array(); + if (empty($srcset_data)) { + return $responsive_attributes; + } + + $breakpoints = null; + + if (!array_key_exists("srcset", $attributes)) { + $breakpoints = get_or_generate_breakpoints($public_id, $srcset_data, $options); + $transformation = Cloudinary::option_get($srcset_data, "transformation"); + $srcset_attr = generate_srcset_attribute($public_id, $breakpoints, $transformation, $options); + if (!is_null($srcset_attr)) { + $responsive_attributes["srcset"] = $srcset_attr; + } + } + + if (!array_key_exists("sizes", $attributes) && Cloudinary::option_get($srcset_data, "sizes") === true) { + if (is_null($breakpoints)) { + $breakpoints = get_or_generate_breakpoints($public_id, $srcset_data, $options); + } + + $sizes_attr = generate_sizes_attribute($breakpoints); + if (!is_null($sizes_attr)) { + $responsive_attributes["sizes"] = $sizes_attr; + } + } + + return $responsive_attributes; + } + /** + * Generates HTML img tag + * + * @api + * + * @param string $public_id Public ID of the resource + * + * @param array $options Additional options + * + * Examples: + * + * W/H are not sent to cloudinary + * cl_image_tag("israel.png", array("width"=>100, "height"=>100, "alt"=>"hello") + * + * W/H are sent to cloudinary + * cl_image_tag("israel.png", array("width"=>100, "height"=>100, "alt"=>"hello", "crop"=>"fit") + * + * @return string Resulting img tag + * + */ + function cl_image_tag($public_id, $options = array()) + { + $original_options = $options; + + $attributes = Cloudinary::option_consume($options, 'attributes', array()); + + $srcset_option = Cloudinary::option_consume($options, 'srcset', []); + + $srcset_data = []; + + if (!is_array($srcset_option)) { + $attributes = array_merge(["srcset" => $srcset_option], $attributes); + } + else { + $srcset_data = array_merge(Cloudinary::config_get("srcset", []), $srcset_option); + } + + $source = cloudinary_url_internal($public_id, $options); + if (isset($options["html_width"])) { + $options["width"] = Cloudinary::option_consume($options, "html_width"); + } + if (isset($options["html_height"])) { + $options["height"] = Cloudinary::option_consume($options, "html_height"); + } + + $client_hints = Cloudinary::option_consume($options, "client_hints", Cloudinary::config_get("client_hints")); + $responsive = Cloudinary::option_consume($options, "responsive"); + $hidpi = Cloudinary::option_consume($options, "hidpi"); + if (($responsive || $hidpi) && !$client_hints) { + $options["data-src"] = $source; + $classes = array($responsive ? "cld-responsive" : "cld-hidpi"); + $current_class = Cloudinary::option_consume($options, "class"); + if ($current_class) { + array_unshift($classes, $current_class); + } + $options["class"] = implode(" ", $classes); + $source = Cloudinary::option_consume( + $options, + "responsive_placeholder", + Cloudinary::config_get("responsive_placeholder") + ); + if ($source == "blank") { + $source = Cloudinary::BLANK; + } + } + + $responsive_attrs = generate_image_responsive_attributes( + $public_id, + $attributes, + $srcset_data, + $original_options + ); + if (!empty($responsive_attrs)) { + $size_attributes = array("width", "height"); + foreach ($size_attributes as $key) { + unset($options[$key]); + } + } + + // Explicitly provided attributes override options + $attributes = array_merge($options, $responsive_attrs, $attributes); + + $html = ""; + + return $html; + } + + function fetch_image_tag($url, $options = array()) + { + $options["type"] = "fetch"; + + return cl_image_tag($url, $options); + } + + function facebook_profile_image_tag($profile, $options = array()) + { + $options["type"] = "facebook"; + + return cl_image_tag($profile, $options); + } + + function gravatar_profile_image_tag($email, $options = array()) + { + $options["type"] = "gravatar"; + $options["format"] = "jpg"; + + return cl_image_tag(md5(strtolower(trim($email))), $options); + } + + function twitter_profile_image_tag($profile, $options = array()) + { + $options["type"] = "twitter"; + + return cl_image_tag($profile, $options); + } + + function twitter_name_profile_image_tag($profile, $options = array()) + { + $options["type"] = "twitter_name"; + + return cl_image_tag($profile, $options); + } + + function cloudinary_js_config() + { + $params = array(); + foreach (Cloudinary::$JS_CONFIG_PARAMS as $param) { + $value = Cloudinary::config_get($param); + if ($value) { + $params[$param] = $value; + } + } + + return "\n"; + } + + function cloudinary_url($source, $options = array()) + { + return cloudinary_url_internal($source, $options); + } + + function cloudinary_url_internal($source, &$options = array()) + { + if (!isset($options["secure"])) { + $options["secure"] = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || + (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'); + } + + return Cloudinary::cloudinary_url($source, $options); + } + + function cl_sprite_url($tag, $options = array()) + { + if (substr($tag, -strlen(".css")) != ".css") { + $options["format"] = "css"; + } + $options["type"] = "sprite"; + + return cloudinary_url_internal($tag, $options); + } + + function cl_sprite_tag($tag, $options = array()) + { + return ""; + } + + function default_poster_options() + { + return array('format' => 'jpg', 'resource_type' => 'video'); + } + + function default_source_types() + { + return array('webm', 'mp4', 'ogv'); + } + + # Returns a url for the given source with +options+ + function cl_video_path($source, $options = array()) + { + $options = array_merge(array('resource_type' => 'video'), $options); + + return cloudinary_url_internal($source, $options); + } + + # Returns an HTML img tag with the thumbnail for the given video +source+ and +options+ + function cl_video_thumbnail_tag($source, $options = array()) + { + return cl_image_tag($source, array_merge(default_poster_options(), $options)); + } + + # Returns a url for the thumbnail for the given video +source+ and +options+ + function cl_video_thumbnail_path($source, $options = array()) + { + $options = array_merge(default_poster_options(), $options); + + return cloudinary_url_internal($source, $options); + } + + /** + * @internal + * Helper function for cl_video_tag, collects remaining options and returns them as attributes + * + * @param array $video_options Remaining options + * + * @return array Resulting attributes + */ + function collect_video_tag_attributes($video_options) + { + $attributes = $video_options; + + if (isset($attributes["html_width"])) { + $attributes['width'] = Cloudinary::option_consume($attributes, 'html_width'); + } + + if (isset($attributes['html_height'])) { + $attributes['height'] = Cloudinary::option_consume($attributes, 'html_height'); + } + + if (empty($attributes['poster'])) { + unset($attributes['poster']); + } + + return $attributes; + } + + /** + * @internal + * Helper function for cl_video_tag, generates video poster URL + * + * @param string $source The public ID of the resource + * @param array $video_options Additional options + * + * @return string Resulting video poster URL + */ + function generate_video_poster_attr($source, $video_options) + { + if (!array_key_exists('poster', $video_options)) { + return cl_video_thumbnail_path($source, $video_options); + } + + if (!is_array($video_options['poster'])) { + return $video_options['poster']; + } + + if (!array_key_exists('public_id', $video_options['poster'])) { + return cl_video_thumbnail_path($source, $video_options['poster']); + } + + return cloudinary_url_internal($video_options['poster']['public_id'], $video_options['poster']); + } + + /** + * @internal + * Helper function for cl_video_tag, generates video mime type from source_type and codecs + * + * @param string $source_type The type of the source + * + * @param string|array $codecs Codecs + * + * @return string Resulting mime type + */ + function video_mime_type($source_type, $codecs = null) + { + $video_type = (($source_type == 'ogv') ? 'ogg' : $source_type); + + if (empty($source_type)) { + return null; + } + + $codecs_str = is_array($codecs) ? implode(', ', $codecs) : $codecs; + $codecs_str = !empty($codecs_str) ? "codecs=$codecs_str" : $codecs_str; + + return implode('; ', array_filter(["video/$video_type", $codecs_str])); + } + + /** + * @internal + * Helper function for cl_video_tag, populates source tags from provided options. + * + * source_types and sources are mutually exclusive, only one of them can be used. + * If both are not provided, source types are used (for backwards compatibility) + * + * @param string $source The public ID of the video + * @param array $options Additional options + * + * @return array Resulting source tags (may be empty) + */ + function populate_video_source_tags($source, &$options) + { + $source_tags = []; + // Consume all relevant options, otherwise they are left and passed as attributes + $sources = Cloudinary::option_consume($options, 'sources', null); + $source_types = Cloudinary::option_consume($options, 'source_types', null); + $source_transformation = Cloudinary::option_consume($options, 'source_transformation', array()); + + if (is_array($sources) && !empty($sources)) { + foreach ($sources as $source_data) { + $transformations = Cloudinary::option_get($source_data, "transformations", array()); + $transformation = array_merge($options, $transformations); + $source_type = Cloudinary::option_get($source_data, "type"); + $src = cl_video_path($source . '.' . $source_type, $transformation); + $codecs = Cloudinary::option_get($source_data, "codecs"); + $attributes = ['src' => $src, 'type' => video_mime_type($source_type, $codecs)]; + array_push($source_tags, ''); + } + + return $source_tags; + } + + if (empty($source_types)) { + $source_types = default_source_types(); + } + + if (!is_array($source_types)) { + return $source_tags; + } + + foreach ($source_types as $source_type) { + $transformation = Cloudinary::option_consume($source_transformation, $source_type, array()); + $transformation = array_merge($options, $transformation); + $src = cl_video_path($source . '.' . $source_type, $transformation); + $attributes = ['src' => $src, 'type' => video_mime_type($source_type)]; + array_push($source_tags, ''); + } + + return $source_tags; + } + + /** + * @api + * Creates an HTML video tag for the provided source + * + * @param string $source The public ID of the video + * @param array $options Additional options + * + * @return string Resulting video tag + */ + function cl_video_tag($source, $options = array()) + { + $source = preg_replace('/\.(' . implode('|', default_source_types()) . ')$/', '', $source); + + $attributes = Cloudinary::option_consume($options, 'attributes', array()); + + $fallback = Cloudinary::option_consume($options, 'fallback_content', ''); + + # Save source types for a single video source handling (it can be a single type) + $source_types = Cloudinary::option_get($options, 'source_types', ""); + + if (!array_key_exists("poster", $attributes)) { + $options['poster'] = generate_video_poster_attr($source, $options); + } + + $options = array_merge(['resource_type' => 'video'], $options); + + $source_tags = populate_video_source_tags($source, $options); + + if (empty($source_tags)) { + $source .= '.' . $source_types; + } + + $src = cloudinary_url_internal($source, $options); + + if (empty($source_tags)) { + $attributes['src'] = $src; + } + + $attributes = array_merge(collect_video_tag_attributes($options), $attributes); + + $html = ''; + + return $html; + } + + /** + * @internal + * Generates `media` attribute of the `source` tag + * + * @param array $attributes Attributes + * @param array $media_options Currently only supported `min_width` and `max_width` + * + * @return null|string Media attribute + */ + function generate_media_attr($media_options) + { + $media_query_conditions = []; + + if (!empty($media_options['min_width'])) { + array_push($media_query_conditions, "(min-width: ${media_options['min_width']}px)"); + } + + if (!empty($media_options['max_width'])) { + array_push($media_query_conditions, "(max-width: ${media_options['max_width']}px)"); + } + + if (empty($media_query_conditions)) { + return null; + } + + return implode(' and ', $media_query_conditions); + } + + /** + * @api Generates HTML `source` tag that can be used by `picture` tag + * + * @param $public_id + * @param $options + * + * @return string + */ + function cl_source_tag($public_id, $options = []) + { + $srcset_data = array_merge( + Cloudinary::config_get("srcset", []), + Cloudinary::option_consume($options, 'srcset', []) + ); + + $attributes = Cloudinary::option_get($options, 'attributes', []); + + $responsive_attrs = generate_image_responsive_attributes( + $public_id, + $attributes, + $srcset_data, + $options + ); + + $attributes = array_merge($responsive_attrs, $attributes); + + // `source` tag under `picture` tag uses `srcset` attribute for both `srcset` and `src` urls + if (!array_key_exists("srcset", $attributes)) { + $attributes["srcset"] = cloudinary_url($public_id, $options); + } + + $media_attr = generate_media_attr(Cloudinary::option_get($options, "media")); + if (!empty($media_attr)) { + $attributes["media"] = $media_attr; + } + + return ''; + } + + /** + * @api Generates HTML `picture` tag + * + * @param string $public_id Public ID of the source image + * @param array $options Common options for all sources and `img` tag + * @param array $sources Definitions of each source which contains min_width, max_width and transformation + * + * @return string + */ + function cl_picture_tag($public_id, $options = [], $sources = []) + { + $tag = ''; + + $public_id = Cloudinary::check_cloudinary_field($public_id, $options); + Cloudinary::patch_fetch_format($options); + foreach ($sources as $source) { + $source_options = $options; + $source_options = Cloudinary::chain_transformations( + $source_options, + Cloudinary::option_get($source, "transformation") + ); + $source_options["media"] = Cloudinary::array_subset($source, ['min_width', 'max_width']); + + $tag .= cl_source_tag($public_id, $source_options); + } + + $tag .= cl_image_tag($public_id, $options); + + $tag .= ''; + + return $tag; + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/HttpClient.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/HttpClient.php new file mode 100644 index 0000000..bc621d1 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/HttpClient.php @@ -0,0 +1,106 @@ +timeout = \Cloudinary::option_get($options, "timeout", self::DEFAULT_HTTP_TIMEOUT); + } + + /** + * Get JSON as associative array from specified URL + * + * @param string $url URL of the JSON + * + * @return array Associative array that represents JSON object + * + * @throws Error + */ + public function getJSON($url) + { + $ch = curl_init($url); + + curl_setopt($ch, CURLOPT_HEADER, false); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET"); + curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_USERAGENT, \Cloudinary::userAgent()); + + $response = $this->execute($ch); + + $curl_error = null; + if (curl_errno($ch)) { + $curl_error = curl_error($ch); + } + + curl_close($ch); + + if ($curl_error != null) { + throw new Error("Error in sending request to server - " . $curl_error); + } + + if ($response->responseCode != 200) { + throw new Error("Server returned unexpected status code - {$response->responseCode} - {$response->body}"); + } + + return self::parseJSONResponse($response); + } + + /** + * Executes HTTP request, parses response headers, leaves body as a string + * + * Based on http://snipplr.com/view/17242/ + * + * @param resource $ch cURL handle + * + * @return \stdClass Containing headers, body, responseCode properties + */ + protected static function execute($ch) + { + $content = curl_exec($ch); + $result = new \stdClass; + $result->body = trim($content); + $result->responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + return $result; + } + + /** + * Parses JSON string from response body. + * + * @param \stdClass $response Class representing response + * + * @return mixed Decoded JSON object + * + * @throws Error + */ + protected static function parseJSONResponse($response) + { + $result = json_decode($response->body, true); + if ($result == null) { + $error = json_last_error(); + throw new Error( + "Error parsing server response ({$response->responseCode}) - {$response->body}. Got - {$error}" + ); + } + + return $result; + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/PreloadedFile.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/PreloadedFile.php new file mode 100644 index 0000000..e511c62 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/PreloadedFile.php @@ -0,0 +1,81 @@ +resource_type = $matches[1]; + $this->type = $matches[2]; + $this->version = $matches[3]; + $this->filename = $matches[4]; + $this->signature = $matches[5]; + $public_id_and_format = $this->split_format($this->filename); + $this->public_id = $public_id_and_format[0]; + $this->format = $public_id_and_format[1]; + } + + public function is_valid() + { + $public_id = $this->resource_type == 'raw' ? $this->filename : $this->public_id; + $expected_signature = \Cloudinary::api_sign_request( + array( + "public_id" => $public_id, + "version" => $this->version, + ), + \Cloudinary::config_get("api_secret") + ); + + return $this->signature == $expected_signature; + } + + protected function split_format($identifier) + { + $last_dot = strrpos($identifier, '.'); + + if ($last_dot === false) { + return array($identifier, null); + } + $public_id = substr($identifier, 0, $last_dot); + $format = substr($identifier, $last_dot + 1); + + return array($public_id, $format); + } + + public function identifier() + { + return "v{$this->version}/{$this->filename}"; + } + + public function extended_identifier() + { + return "{$this->resource_type}/{$this->type}/{$this->identifier()}"; + } + + public function __toString() + { + return "{$this->resource_type}/{$this->type}/v{$this->version}/{$this->filename}#{$this->signature}"; + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Search.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Search.php new file mode 100644 index 0000000..9d9bcd6 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Search.php @@ -0,0 +1,82 @@ +query_hash = array( + 'sort_by' => array(), + 'aggregate' => array(), + 'with_field' => array(), + ); + } + + public function expression($value) + { + $this->query_hash['expression'] = $value; + + return $this; + } + + public function max_results($value) + { + $this->query_hash['max_results'] = $value; + + return $this; + } + + public function next_cursor($value) + { + $this->query_hash['next_cursor'] = $value; + + return $this; + } + + public function sort_by($field_name, $dir = 'desc') + { + array_push($this->query_hash['sort_by'], array($field_name => $dir)); + + return $this; + } + + public function aggregate($value) + { + array_push($this->query_hash['aggregate'], $value); + + return $this; + } + + public function with_field($value) + { + array_push($this->query_hash['with_field'], $value); + + return $this; + } + + public function as_array() + { + return array_filter( + $this->query_hash, + function ($value) { + return ((is_array($value) && !empty($value)) || ($value != null)); + } + ); + } + + public function execute($options = array()) + { + $api = new Api(); + $uri = array('resources/search'); + $options = array_merge($options, array('content_type' => 'application/json')); + $method = 'post'; + + return $api->call_api($method, $uri, $this->as_array(), $options); + } + } + +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/SignatureVerifier.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/SignatureVerifier.php new file mode 100644 index 0000000..80fa3fd --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/SignatureVerifier.php @@ -0,0 +1,169 @@ + allowed_types for notification signature validator + */ + private static $NOTIFICATION_VALIDATOR_ALLOWED_TYPES = [ + 'body' => 'string', + 'timestamp' => 'int|string', + 'signature' => 'string', + 'validFor' => 'int|string' + ]; + + /** + * @var array of parameter => allowed_types for API response signature validator + */ + private static $API_RESPONSE_VALIDATOR_ALLOWED_TYPES = [ + 'publicId' => 'string', + 'version' => 'int|string', + 'signature' => 'string', + ]; + + /** + * Verifies the authenticity of a notification signature + * + * @param string $body Json of the request's body + * @param int|string $timestamp Unix timestamp. Can be retrieved from the X-Cld-Timestamp header + * @param string $signature Actual signature. Can be retrieved from the X-Cld-Signature header + * @param int|string $validFor The desired time in seconds for considering the request valid + * + * @return boolean + * + * @throws \InvalidArgumentException In case a mandatory parameter is empty or of wrong type + */ + public static function verifyNotificationSignature($body, $timestamp, $signature, $validFor = 7200) + { + $paramsArray = [ + 'body' => $body, + 'timestamp' => $timestamp, + 'signature' => $signature, + 'validFor' => $validFor + ]; + + self::validateParams($paramsArray, self::$NOTIFICATION_VALIDATOR_ALLOWED_TYPES); + + if (time() - $timestamp > $validFor) { + return false; + } + + $apiSecret = \Cloudinary::config_get('api_secret'); + self::validateApiSecret($apiSecret); + + $payloadToSign = $body . $timestamp; + $hmac = self::generateHmac($payloadToSign, $apiSecret); + + if ($hmac !== $signature) { + return false; + } + + return true; + } + + /** + * Verifies the authenticity of an API response signature + * + * @param string $publicId The public id of the asset as returned in the API response + * @param int|string $version The version of the asset as returned in the API response + * @param string $signature Actual signature. Can be retrieved from the X-Cld-Signature header + * + * @return boolean + * + * @throws \InvalidArgumentException in case a mandatory parameter is empty or of wrong type + */ + public static function verifyApiResponseSignature($publicId, $version, $signature) + { + $paramsArray = ['publicId' => $publicId, 'version' => $version, 'signature' => $signature]; + + self::validateParams($paramsArray, self::$API_RESPONSE_VALIDATOR_ALLOWED_TYPES); + + $apiSecret = \Cloudinary::config_get('api_secret'); + self::validateApiSecret($apiSecret); + + $payloadToSign = 'public_id=' . $publicId . '&version=' . $version; + $hmac = self::generateHmac($payloadToSign, $apiSecret); + + if ($hmac !== $signature) { + return false; + } + + return true; + } + + /** + * Validates parameters + * + * @param array $params Parameters to validate + * @param array $allowedTypes The allowed type/s of the parameters. Pipe delimiter for multiple values + * + * @throws \InvalidArgumentException In case a mandatory parameter is empty or of wrong type + */ + private static function validateParams($params, $allowedTypes) + { + foreach ($allowedTypes as $param => $types) { + if (empty($params[$param])) { + throw new \InvalidArgumentException("$param parameter cannot be empty"); + } + + if (!self::paramValidator($params[$param], $types)) { + throw new \InvalidArgumentException("$param must be one of the following types: $types"); + } + } + } + + /** + * Validates the type of a single parameter + * + * @param mixed $param Parameter to validate + * @param string $type The allowed type/s of the parameter. Pipe delimiter for multiple values + * + * @return boolean + */ + private static function paramValidator($param, $type) + { + $allowedTypes = explode('|', $type); + + foreach ($allowedTypes as $allowedType) { + $validationFunction = 'is_' . $allowedType; + if ($validationFunction($param)) { + return true; + } + } + + return false; + } + + /** + * Validates API secret + * + * @param string $apiSecret The API secret + * + * @throws \InvalidArgumentException In case API secret is missing or invalid + */ + private static function validateApiSecret($apiSecret) + { + if (empty($apiSecret) || !is_string($apiSecret)) { + throw new \InvalidArgumentException('API Secret is invalid'); + } + } + + /** + * Generates hmac to compare against signature + * + * @param string $payloadToSign The payload to sign + * @param string $apiSecret The API secret + * + * @return string + */ + public static function generateHmac($payloadToSign, $apiSecret) + { + return sha1($payloadToSign . $apiSecret); + } +} diff --git a/lib/Cloudinary/Uploader.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Uploader.php similarity index 52% rename from lib/Cloudinary/Uploader.php rename to lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Uploader.php index 0d686df..a96ef15 100644 --- a/lib/Cloudinary/Uploader.php +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Uploader.php @@ -1,12 +1,24 @@ time(), + $params = array( + "timestamp" => time(), + "access_control" => \Cloudinary::encode_array_to_json( + \Cloudinary::option_get($options, "access_control") + ), "allowed_formats" => \Cloudinary::encode_array(\Cloudinary::option_get($options, "allowed_formats")), "async" => \Cloudinary::option_get($options, "async"), "auto_tagging" => \Cloudinary::option_get($options, "auto_tagging"), @@ -14,22 +26,28 @@ public static function build_upload_params(&$options) "backup" => \Cloudinary::option_get($options, "backup"), "callback" => \Cloudinary::option_get($options, "callback"), "categorization" => \Cloudinary::option_get($options, "categorization"), + "cinemagraph_analysis" => \Cloudinary::option_get($options, "cinemagraph_analysis"), "colors" => \Cloudinary::option_get($options, "colors"), "context" => \Cloudinary::encode_assoc_array(\Cloudinary::option_get($options, "context")), - "custom_coordinates" => \Cloudinary::encode_double_array(\Cloudinary::option_get($options, "custom_coordinates")), + "custom_coordinates" => \Cloudinary::encode_double_array( + \Cloudinary::option_get($options, "custom_coordinates") + ), "detection" => \Cloudinary::option_get($options, "detection"), "discard_original_filename" => \Cloudinary::option_get($options, "discard_original_filename"), "eager" => Uploader::build_eager(\Cloudinary::option_get($options, "eager")), "eager_async" => \Cloudinary::option_get($options, "eager_async"), "eager_notification_url" => \Cloudinary::option_get($options, "eager_notification_url"), "exif" => \Cloudinary::option_get($options, "exif"), - "face_coordinates" => \Cloudinary::encode_double_array(\Cloudinary::option_get($options, "face_coordinates")), + "face_coordinates" => \Cloudinary::encode_double_array( + \Cloudinary::option_get($options, "face_coordinates") + ), "faces" => \Cloudinary::option_get($options, "faces"), "folder" => \Cloudinary::option_get($options, "folder"), "format" => \Cloudinary::option_get($options, "format"), "headers" => Uploader::build_custom_headers(\Cloudinary::option_get($options, "headers")), "image_metadata" => \Cloudinary::option_get($options, "image_metadata"), "invalidate" => \Cloudinary::option_get($options, "invalidate"), + "metadata" => \Cloudinary::encode_assoc_array(\Cloudinary::option_get($options, "metadata")), "moderation" => \Cloudinary::option_get($options, "moderation"), "notification_url" => \Cloudinary::option_get($options, "notification_url"), "ocr" => \Cloudinary::option_get($options, "ocr"), @@ -37,6 +55,8 @@ public static function build_upload_params(&$options) "phash" => \Cloudinary::option_get($options, "phash"), "proxy" => \Cloudinary::option_get($options, "proxy"), "public_id" => \Cloudinary::option_get($options, "public_id"), + "quality_analysis" => \Cloudinary::option_get($options, "quality_analysis"), + "quality_override" => \Cloudinary::option_get($options, "quality_override"), "raw_convert" => \Cloudinary::option_get($options, "raw_convert"), "return_delete_token" => \Cloudinary::option_get($options, "return_delete_token"), "similarity_search" => \Cloudinary::option_get($options, "similarity_search"), @@ -46,29 +66,65 @@ public static function build_upload_params(&$options) "unique_filename" => \Cloudinary::option_get($options, "unique_filename"), "upload_preset" => \Cloudinary::option_get($options, "upload_preset"), "use_filename" => \Cloudinary::option_get($options, "use_filename"), - "responsive_breakpoints" => Uploader::build_responsive_breakpoints(\Cloudinary::option_get($options, "responsive_breakpoints")) + "responsive_breakpoints" => Uploader::build_responsive_breakpoints( + \Cloudinary::option_get($options, "responsive_breakpoints") + ), + ); + array_walk( + $params, + function (&$value, $key) { + $value = (is_bool($value) ? ($value ? "1" : "0") : $value); + } + ); + + return array_filter( + $params, + function ($v) { + return !is_null($v) && ($v !== ""); + } ); - array_walk($params, function (&$value, $key){ $value = (is_bool($value) ? ($value ? "1" : "0") : $value);}); - return array_filter($params,function($v){ return !is_null($v) && ($v !== "" );}); } public static function unsigned_upload($file, $upload_preset, $options = array()) { - return Uploader::upload($file, array_merge($options, array("unsigned"=>TRUE, "upload_preset"=>$upload_preset))); + return Uploader::upload( + $file, + array_merge($options, array("unsigned" => true, "upload_preset" => $upload_preset)) + ); } public static function upload($file, $options = array()) { $params = Uploader::build_upload_params($options); - return Uploader::call_api("upload", $params, $options, $file); + + return Uploader::call_cacheable_api("upload", $params, $options, $file); } - // Upload large raw files. Note that public_id should include an extension for best results. - public static function upload_large($file, $options=array()) + /** + * Upload large files. Note that public_id should include an extension for best results. + * + * @param string $file The file to upload + * @param array $options Additional options + * + * @return mixed|null + * @throws \Exception + */ + public static function upload_large($file, $options = array()) { + if (preg_match(self::REMOTE_URL_REGEX, $file)) { + return self::upload($file, $options); + } + $src = fopen($file, 'r'); - $temp_file_name = tempnam(sys_get_temp_dir(), 'cldupload.' . pathinfo($file, PATHINFO_EXTENSION)); - $upload = $upload_id = NULL; + + // If not provided, preserve original file name in the upload + $options["filename"] = \Cloudinary::option_get($options, 'filename', $file); + + $file_extension = pathinfo($file, PATHINFO_EXTENSION); + $temp_file_name = tempnam(sys_get_temp_dir(), 'cldupload.') . + (!empty($file_extension) ? "." . $file_extension : ""); + $upload = null; + $upload_id = \Cloudinary::random_public_id(); $chunk_size = \Cloudinary::option_get($options, "chunk_size", 20000000); $public_id = \Cloudinary::option_get($options, "public_id"); $index = 0; @@ -78,26 +134,29 @@ public static function upload_large($file, $options=array()) if ($current_loc >= $file_size) { break; } + $dest = fopen($temp_file_name, 'w'); stream_copy_to_stream($src, $dest, $chunk_size); fclose($dest); - if (phpversion() >= "5.3.0") { - clearstatcache(TRUE, $temp_file_name); - } else { - clearstatcache(); - } + + clearstatcache(true, $temp_file_name); $temp_file_size = filesize($temp_file_name); - $range = "bytes ". $current_loc . "-" . ($current_loc + $temp_file_size - 1) . "/" . $file_size; + $range = "bytes " . $current_loc . "-" . ($current_loc + $temp_file_size - 1) . "/" . $file_size; try { - $upload = Uploader::upload_large_part($temp_file_name, array_merge($options, - array("public_id"=>$public_id, "content_range"=>$range))); - } catch(\Exception $e) { + $upload = Uploader::upload_large_part( + $temp_file_name, + array_merge($options, array( + "public_id" => $public_id, + "content_range" => $range, + "x_unique_upload_id" => $upload_id + )) + ); + } catch (\Exception $e) { unlink($temp_file_name); fclose($src); - throw $e; + throw $e; } - $upload_id = \Cloudinary::option_get($upload, "upload_id"); $public_id = \Cloudinary::option_get($upload, "public_id"); $index += 1; } @@ -105,13 +164,15 @@ public static function upload_large($file, $options=array()) fclose($src); return $upload; } - - // Upload large raw files. Note that public_id should include an extension for best results. - public static function upload_large_part($file, $options=array()) + + // Upload large files. Note that public_id should include an extension for best results. + public static function upload_large_part($file, $options = array()) { $params = Uploader::build_upload_params($options); - return Uploader::call_api("upload_chunked", $params, array_merge(array("resource_type" => "raw"), $options), $file); + $full_options = array_merge(array("resource_type" => "raw"), $options); + + return Uploader::call_cacheable_api("upload_chunked", $params, $full_options, $file); } public static function destroy($public_id, $options = array()) @@ -120,8 +181,9 @@ public static function destroy($public_id, $options = array()) "timestamp" => time(), "type" => \Cloudinary::option_get($options, "type"), "invalidate" => \Cloudinary::option_get($options, "invalidate"), - "public_id" => $public_id + "public_id" => $public_id, ); + return Uploader::call_api("destroy", $params, $options); } @@ -136,27 +198,62 @@ public static function rename($from_public_id, $to_public_id, $options = array() "overwrite" => \Cloudinary::option_get($options, "overwrite"), "to_type" => \Cloudinary::option_get($options, "to_type"), ); + return Uploader::call_api("rename", $params, $options); } - + + /** + * Populates metadata fields with the given values. Existing values will be overwritten. + * + * Any metadata-value pairs given are merged with any existing metadata-value pairs + * (an empty value for an existing metadata field clears the value) + * + * @param array $metadata A list of custom metadata fields (by external_id) and the values to assign to each + * of them. + * @param array $public_ids An array of Public IDs of assets uploaded to Cloudinary. + * @param array $options { + * + * @var string resource_type The type of file. Default: image. Valid values: image, raw, video. + * @var string type The storage type. Default: upload. + * Valid values: upload, private, authenticated + * } + * + * @return mixed a list of public IDs that were updated + * + * @throws Error + */ + public static function update_metadata($metadata, $public_ids, $options = array()) + { + $params = array( + "timestamp" => time(), + "metadata" => \Cloudinary::encode_assoc_array($metadata), + "public_ids" => \Cloudinary::build_array($public_ids), + "type" => \Cloudinary::option_get($options, "type"), + ); + + return Uploader::call_api("metadata", $params, $options); + } + public static function explicit($public_id, $options = array()) { $options["public_id"] = $public_id; $params = Uploader::build_upload_params($options); - return Uploader::call_api("explicit", $params, $options); + + return Uploader::call_cacheable_api("explicit", $params, $options); } public static function generate_sprite($tag, $options = array()) { - $transformation = \Cloudinary::generate_transformation_string( - array_merge(array("fetch_format"=>\Cloudinary::option_get($options, "format")), $options)); + $transOptions = array_merge(array("fetch_format" => \Cloudinary::option_get($options, "format")), $options); + $transformation = \Cloudinary::generate_transformation_string($transOptions); $params = array( "timestamp" => time(), "tag" => $tag, "async" => \Cloudinary::option_get($options, "async"), "notification_url" => \Cloudinary::option_get($options, "notification_url"), - "transformation" => $transformation + "transformation" => $transformation, ); + return Uploader::call_api("sprite", $params, $options); } @@ -169,8 +266,9 @@ public static function multi($tag, $options = array()) "format" => \Cloudinary::option_get($options, "format"), "async" => \Cloudinary::option_get($options, "async"), "notification_url" => \Cloudinary::option_get($options, "notification_url"), - "transformation" => $transformation + "transformation" => $transformation, ); + return Uploader::call_api("multi", $params, $options); } @@ -183,8 +281,9 @@ public static function explode($public_id, $options = array()) "format" => \Cloudinary::option_get($options, "format"), "type" => \Cloudinary::option_get($options, "type"), "notification_url" => \Cloudinary::option_get($options, "notification_url"), - "transformation" => $transformation + "transformation" => $transformation, ); + return Uploader::call_api("explode", $params, $options); } @@ -198,6 +297,18 @@ public static function remove_tag($tag, $public_ids = array(), $options = array( return Uploader::call_tags_api($tag, "remove", $public_ids, $options); } + /** + * Remove all tags from the specified public IDs. + * + * @param array|string $public_ids the public IDs of the resources to update + * @param array $options additional options passed to the request + * @return mixed list of public IDs that were updated + */ + public static function remove_all_tags($public_ids, $options = array()) + { + return Uploader::call_tags_api(null, "remove_all", $public_ids, $options); + } + public static function replace_tag($tag, $public_ids = array(), $options = array()) { return Uploader::call_tags_api($tag, "replace", $public_ids, $options); @@ -210,17 +321,20 @@ public static function call_tags_api($tag, $command, $public_ids = array(), &$op "tag" => $tag, "public_ids" => \Cloudinary::build_array($public_ids), "type" => \Cloudinary::option_get($options, "type"), - "command" => $command + "command" => $command, ); + return Uploader::call_api("tags", $params, $options); } - public static function add_context($context, $public_ids = array(), $options = array()) { - return Uploader::call_context_api($context, 'add', $public_ids, $options); + public static function add_context($context, $public_ids = array(), $options = array()) + { + return Uploader::call_context_api($context, 'add', $public_ids, $options); } - public static function remove_all_context($public_ids = array(), $options = array()) { - return Uploader::call_context_api(null, 'remove_all', $public_ids, $options); + public static function remove_all_context($public_ids = array(), $options = array()) + { + return Uploader::call_context_api(null, 'remove_all', $public_ids, $options); } public static function call_context_api($context, $command, $public_ids = array(), &$options = array()) @@ -230,12 +344,24 @@ public static function call_context_api($context, $command, $public_ids = array( "context" => $context, "public_ids" => \Cloudinary::build_array($public_ids), "type" => \Cloudinary::option_get($options, "type"), - "command" => $command + "command" => $command, ); + return Uploader::call_api("context", $params, $options); } - private static $TEXT_PARAMS = array("public_id", "font_family", "font_size", "font_color", "text_align", "font_weight", "font_style", "background", "opacity", "text_decoration"); + private static $TEXT_PARAMS = array( + "public_id", + "font_family", + "font_size", + "font_color", + "text_align", + "font_weight", + "font_style", + "background", + "opacity", + "text_decoration", + ); public static function text($text, $options = array()) { @@ -243,16 +369,18 @@ public static function text($text, $options = array()) foreach (Uploader::$TEXT_PARAMS as $key) { $params[$key] = \Cloudinary::option_get($options, $key); } + return Uploader::call_api("text", $params, $options); } # Creates a new archive in the server and returns information in JSON format - public static function create_archive($options = array(), $target_format = NULL) + public static function create_archive($options = array(), $target_format = null) { $params = \Cloudinary::build_archive_params($options); - if ($target_format != NULL) { + if ($target_format != null) { $params["target_format"] = $target_format; } + return Uploader::call_api("generate_archive", $params, $options); } @@ -262,7 +390,42 @@ public static function create_zip($options = array()) return Uploader::create_archive($options, "zip"); } - public static function call_api($action, $params, $options = array(), $file = NULL) + /** + * Calls Upload API and saves results to cache (if enabled) + * + * @param string $action Action to call + * @param array $params Array of parameters + * @param array|null $options Optional. Additional options + * @param string|null $file Optional. File to upload + * + * @return mixed + * + * @throws Error + */ + public static function call_cacheable_api($action, $params, $options = array(), $file = null) + { + $result = self::call_api($action, $params, $options, $file); + + if (\Cloudinary::option_get($options, "use_cache", \Cloudinary::config_get("use_cache", false))) { + self::save_responsive_breakpoints_to_cache($result); + } + + return $result; + } + + /** + * Perform API call + * + * @param string $action Action to call + * @param array $params Array of parameters + * @param array|null $options Optional. Additional options + * @param string|null $file Optional. File to upload + * + * @return mixed + * + * @throws Error + */ + public static function call_api($action, $params, $options = array(), $file = null) { $return_error = \Cloudinary::option_get($options, "return_error"); if (!\Cloudinary::option_get($options, "unsigned")) { @@ -286,12 +449,14 @@ public static function call_api($action, $params, $options = array(), $file = NU } } if ($file) { - if (!preg_match('/^@|^ftp:|^https?:|^s3:|^data:[^;]*;base64,([a-zA-Z0-9\/+\n=]+)$/', $file)) { + $filename = \Cloudinary::option_get($options, 'filename', $file); + + if (!preg_match(self::REMOTE_URL_REGEX, $file)) { if (function_exists("curl_file_create")) { $post_params['file'] = curl_file_create($file); - $post_params['file']->setPostFilename($file); + $post_params['file']->setPostFilename($filename); } else { - $post_params["file"] = "@" . $file; + $post_params["file"] = "@{$file};filename={$filename}"; } } else { $post_params["file"] = $file; @@ -301,25 +466,43 @@ public static function call_api($action, $params, $options = array(), $file = NU curl_setopt($ch, CURLOPT_POST, true); $timeout = \Cloudinary::option_get($options, "timeout", \Cloudinary::config_get("timeout", 60)); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); - $connection_timeout = \Cloudinary::option_get($options, "connection_timeout", \Cloudinary::config_get("connection_timeout")); - if ($connection_timeout != NULL) { + $connection_timeout = \Cloudinary::option_get( + $options, + "connection_timeout", + \Cloudinary::config_get("connection_timeout") + ); + if ($connection_timeout != null) { curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connection_timeout); } curl_setopt($ch, CURLOPT_POSTFIELDS, $post_params); - curl_setopt($ch, CURLOPT_CAINFO,realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR."cacert.pem"); + curl_setopt($ch, CURLOPT_CAINFO, realpath(dirname(__FILE__)) . DIRECTORY_SEPARATOR . "cacert.pem"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); # no effect since PHP 5.1.3 curl_setopt($ch, CURLOPT_USERAGENT, \Cloudinary::userAgent()); - curl_setopt($ch, CURLOPT_PROXY, \Cloudinary::option_get($options, "api_proxy", \Cloudinary::config_get("api_proxy"))); + curl_setopt( + $ch, + CURLOPT_PROXY, + \Cloudinary::option_get($options, "api_proxy", \Cloudinary::config_get("api_proxy")) + ); + $headers = array(); $range = \Cloudinary::option_get($options, "content_range"); - if ($range != NULL){ - curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Range: '.$range)); + if ($range != null) { + $headers[] = 'Content-Range: ' . $range; + } + $x_unique_upload_id = \Cloudinary::option_get($options, "x_unique_upload_id"); + if ($x_unique_upload_id != null) { + $headers[] = 'X-Unique-Upload-Id: ' . $x_unique_upload_id; + } + if (!empty($headers)) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); } - + $response = curl_exec($ch); - $curl_error = NULL; - if(curl_errno($ch)) - { + + $errno = curl_errno($ch); + + $curl_error = null; + if ($errno != CURLE_OK) { $curl_error = curl_error($ch); } @@ -327,58 +510,136 @@ public static function call_api($action, $params, $options = array(), $file = NU $response_data = $response; curl_close($ch); - if ($curl_error != NULL) { - throw new \Cloudinary\Error("Error in sending request to server - " . $curl_error); + + if ($errno != CURLE_OK) { + $message = "Error in sending request to server"; + + // Provide user a better error message + if ($errno === CURLE_READ_ERROR) { + // Note: race condition can happen here, not that critical, it's only for a message string + if (!file_exists($file)) { + $message .= " - file '{$file}' does not exist"; + } else { + $message .= " - failed reading file '{$file}'"; + } + } else { + $message .= " - $curl_error, errno - $errno"; + } + + throw new Error($message); } + if ($code != 200 && $code != 400 && $code != 500 && $code != 401 && $code != 404) { - throw new \Cloudinary\Error("Server returned unexpected status code - " . $code . " - " . $response_data, $code); + throw new Error( + "Server returned unexpected status code - " . $code . " - " . $response_data, + $code + ); } - $result = json_decode($response_data, TRUE); - if ($result == NULL) { - throw new \Cloudinary\Error("Error parsing server response (" . $code . ") - " . $response_data); + + $result = json_decode($response_data, true); + if ($result == null) { + throw new Error("Error parsing server response (" . $code . ") - " . $response_data); } + if (isset($result["error"])) { if ($return_error) { $result["error"]["http_code"] = $code; } else { - throw new \Cloudinary\Error($result["error"]["message"], $code); + $message = ""; + + if (isset($result["error"]["message"])) { + if (is_array($result["error"]["message"])) { + $message = $result["error"]["message"]["message"]; + } else { + $message = $result["error"]["message"]; + } + } + + throw new Error($message, $code); } } + return $result; } - protected static function build_eager($transformations) { + protected static function build_eager($transformations) + { return \Cloudinary::build_eager($transformations); } - protected static function build_responsive_breakpoints($breakpoints) { + protected static function build_responsive_breakpoints($breakpoints) + { if (!$breakpoints) { - return NULL; + return null; } $breakpoints_params = array(); foreach (\Cloudinary::build_array($breakpoints) as $breakpoint_settings) { if ($breakpoint_settings) { $transformation = \Cloudinary::option_consume($breakpoint_settings, "transformation"); if ($transformation) { - $breakpoint_settings["transformation"] = \Cloudinary::generate_transformation_string($transformation); + $breakpoint_settings["transformation"] = \Cloudinary::generate_transformation_string( + $transformation + ); } array_push($breakpoints_params, $breakpoint_settings); } } + return json_encode($breakpoints_params); } - protected static function build_custom_headers($headers) { - if ($headers == NULL) { - return NULL; + /** + * Saves responsive breakpoints parsed from upload result to cache + * + * @param array $result Upload result + */ + protected static function save_responsive_breakpoints_to_cache($result) + { + if (!array_key_exists("responsive_breakpoints", $result)) { + return; + } + + $public_id = \Cloudinary::option_get($result, "public_id"); + + if (empty($public_id)) { + // We have some faulty result, nothing to cache + return; + } + + $options = \Cloudinary::array_subset($result, ['type', 'resource_type']); + + foreach ($result["responsive_breakpoints"] as $transformation) { + $options["raw_transformation"] = \Cloudinary::option_get($transformation, "transformation", ""); + $options["format"] = pathinfo($transformation["breakpoints"][0]["url"], PATHINFO_EXTENSION); + + // TODO: When updating minimum PHP version to at least 5.5, replace `array_map` with the line below + // $breakpoints = array_column($transformation["breakpoints"], 'width'); + $breakpoints = array_map( + function ($e) { + return $e['width']; + }, + $transformation["breakpoints"] + ); + + ResponsiveBreakpointsCache::instance()->set($public_id, $options, $breakpoints); + } + } + + protected static function build_custom_headers($headers) + { + if ($headers == null) { + return null; } elseif (is_string($headers)) { return $headers; } elseif ($headers == array_values($headers)) { return implode("\n", $headers); } else { - $join_pair = function($key, $value) { return $key . ": " . $value; }; + $join_pair = function ($key, $value) { + return $key . ": " . $value; + }; + return implode("\n", array_map($join_pair, array_keys($headers), array_values($headers))); } - } + } } } diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Utils/Singleton.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Utils/Singleton.php new file mode 100644 index 0000000..320e0a9 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/src/Utils/Singleton.php @@ -0,0 +1,54 @@ + 'crop', 'width' => 100); + protected static $crop_transformation_str = 'c_crop,w_100'; + protected static $encoded_crop_transformation_str = 'c_crop%2Cw_100'; + protected static $scale_transformation = array('crop' => 'scale', 'width' => 100); + protected static $scale_transformation_str = 'c_scale,w_100'; + protected static $encoded_scale_transformation_str = 'c_scale%2Cw_100'; + protected static $transformations; + protected static $arr_of_transformation_str; + protected static $transformations_str; + protected static $encoded_transformations_str; + + protected static $streaming_profile_1; + protected static $streaming_profile_2; + + + + const URL_QUERY_REGEX = "\??(\w+=\w*&?)*"; + + /** @var \Cloudinary\Api $api */ + private $api; + + public static function setUpBeforeClass() + { + if (!Cloudinary::config_get("api_secret")) { + self::markTestSkipped('Please setup environment for Api test to run'); + } + + Curl::$instance = new Curl(); + + self::$api_test_tag = UNIQUE_TEST_TAG; + + self::$api_test = "api_test" . SUFFIX; + self::$api_test_2 = "api_test2" . SUFFIX; + self::$api_test_3 = "api_test,3" . SUFFIX; + self::$api_test_4 = "api_test4" . SUFFIX; + self::$api_test_5 = "api_test5" . SUFFIX; + + self::$api_test_transformation = "api_test_transformation" . SUFFIX; + self::$api_test_transformation_2 = "api_test_transformation2" . SUFFIX; + self::$api_test_transformation_3 = "api_test_transformation3" . SUFFIX; + + self::upload_sample_resources(); + + self::$transformations = array(self::$crop_transformation, self::$scale_transformation); + self::$arr_of_transformation_str = array(self::$crop_transformation_str, self::$scale_transformation_str); + self::$transformations_str = implode("|", self::$arr_of_transformation_str); + $arr_of_encoded_transformation_str = array( + self::$encoded_crop_transformation_str, + self::$encoded_scale_transformation_str + ); + self::$encoded_transformations_str = implode("|", $arr_of_encoded_transformation_str); + + self::$streaming_profile_1 = self::$api_test . "_streaming_profile_1"; + self::$streaming_profile_2 = self::$api_test . "_streaming_profile_2"; + } + + public function tearDown() + { + Curl::$instance = new Curl(); + } + + public static function tearDownAfterClass() + { + if (!Cloudinary::config_get("api_secret")) { + self::fail("You need to configure the cloudinary api for the tests to work."); + } + + $api = new Cloudinary\Api(); + + self::delete_resources($api); + self::delete_transformations($api); + self::delete_streaming_profiles($api); + } + + + /** + * Delete all test related resources + * + * @param \Cloudinary\Api $api an initialized api object + */ + protected static function delete_resources($api) + { + try { + $api->delete_resources(array(self::$api_test, self::$api_test_2, self::$api_test_3, self::$api_test_5)); + $api->delete_resources_by_tag(UNIQUE_TEST_TAG); + } catch (Exception $e) { + } + } + + /** + * Delete all test related transformations + * + * @param \Cloudinary\Api $api an initialized api object + */ + protected static function delete_transformations($api) + { + $transformations = array( + self::$api_test_transformation, + self::$api_test_transformation_2, + self::$api_test_transformation_3, + ); + + foreach ($transformations as $t) { + try { + $api->delete_transformation($t); + } catch (Exception $e) { + } + + } + } + + /** + * Delete all test related streaming profiles + * + * @param \Cloudinary\Api $api + */ + protected static function delete_streaming_profiles($api) + { + $profiles = array( + self::$streaming_profile_1, + self::$streaming_profile_2 + ); + foreach ($profiles as $p) { + try { + $api->delete_streaming_profile($p); + } catch (Exception $e) { + } + } + } + /** + * Upload sample resources. These resources need to be present for some of the tests to work. + */ + protected static function upload_sample_resources() + { + Uploader::upload( + TEST_IMG, + array( + "public_id" => self::$api_test, + "tags" => array(TEST_TAG, UNIQUE_TEST_TAG), + "context" => "key=value", + "eager" => array("transformation" => self::$crop_transformation), + ) + ); + Uploader::upload( + TEST_IMG, + array( + "public_id" => self::$api_test_2, + "tags" => array(TEST_TAG, UNIQUE_TEST_TAG), + "context" => "key=value", + "eager" => array("transformation" => self::$scale_transformation), + ) + ); + } + + + protected function setUp() + { + if (!Cloudinary::config_get("api_secret")) { + $this->markTestSkipped('Please setup environment for API test to run'); + } + if (!isset($this->api)) { + $this->api = new Api(); + } + } + + /** + * Finds element by name and value in an array of arrays + * + * @param array $elements Array to search in + * @param string $attr Attribute name + * @param mixed $value Attribute value + * + * @return null if not found, otherwise found element + */ + protected function find_by_attr($elements, $attr, $value) + { + foreach ($elements as $element) { + if ($element[$attr] == $value) { + return $element; + } + } + return null; + } + + /** + * Should allow listing resource_types + * + * @throws Api\GeneralError + */ + public function test01_resource_types() + { + $result = $this->api->resource_types(); + $this->assertContains("image", $result["resource_types"]); + } + + /** + * Should allow listing resources + * + * @throws Api\GeneralError + */ + public function test02_resources() + { + Curl::mockApi($this); + $this->api->resources(); + Curl::$instance->fields(); + assertUrl($this, "/resources/image"); + } + + /** + * Should allow listing resources with cursor + * + * @throws Api\GeneralError + */ + public function test03_resources_cursor() + { + $result = $this->api->resources(array("max_results" => 1)); + $this->assertNotEquals($result["resources"], null); + $this->assertEquals(count($result["resources"]), 1); + $this->assertNotEquals($result["next_cursor"], null); + + $result2 = $this->api->resources(array("max_results" => 1, "next_cursor" => $result["next_cursor"])); + $this->assertNotEquals($result2["resources"], null); + $this->assertEquals(count($result2["resources"]), 1); + $this->assertNotEquals($result2["resources"][0]["public_id"], $result["resources"][0]["public_id"]); + } + + /** + * Should allow listing resources by type + * + * @throws Api\GeneralError + */ + public function test04_resources_by_type() + { + Curl::mockApi($this); + $this->api->resources(array("type" => "upload", "context" => true, "tags" => true)); + assertUrl($this, "/resources/image/upload"); + assertParam($this, "context", 1); + assertParam($this, "tags", 1); + } + + /** + * Should allow listing resources by prefi + * + * @throws Api\GeneralError + */ + public function test05_resources_by_prefix() + { + Curl::mockApi($this); + $this->api->resources(array("type" => "upload", "prefix" => "api_test", "context" => true, "tags" => true)); + assertUrl($this, "/resources/image/upload"); + assertParam($this, "prefix", "api_test"); + assertParam($this, "context", 1); + assertParam($this, "tags", 1); + } + + /** + * Should allow listing resources by public ids + * + * @throws Api\GeneralError + */ + public function test_resources_by_public_ids() + { + Curl::mockApi($this); + $public_ids = array(self::$api_test, self::$api_test_2, self::$api_test_3); + $this->api->resources_by_ids($public_ids, array("context" => true, "tags" => true)); + assertParam($this, "public_ids", $public_ids); + assertParam($this, "context", 1); + assertParam($this, "tags", 1); + } + + /** + * Should allow listing resources and specify direction + * + * @throws Api\GeneralError + */ + public function test_resources_direction() + { + Curl::mockApi($this); + $this->api->resources_by_tag( + "foobar", + array("type" => "upload", "direction" => "asc") + ); + assertGet($this); + assertUrl($this, "/resources/image/tags/foobar"); + assertParam($this, "direction", "asc"); + $this->api->resources_by_tag( + "foobar", + array("type" => "upload", "direction" => "desc") + ); + assertGet($this); + assertUrl($this, "/resources/image/tags/foobar"); + assertParam($this, "direction", "desc"); + } + + /** + * Should allow listing resources by tag + * + * @throws Api\GeneralError + */ + public function test06_resources_tag() + { + Curl::mockApi($this); + $this->api->resources_by_tag("foobar"); + assertUrl($this, "/resources/image/tags/foobar"); + assertGet($this); + } + + /** + * Should allow listing resources by context + * + * @throws Api\GeneralError + */ + public function test_resources_by_context() + { + Curl::mockApi($this); + $this->api->resources_by_context("k"); + assertGet($this); + assertUrl($this, "/resources/image/context"); + assertParam($this, "key", "k"); + assertNoParam($this, "value"); + + $this->api->resources_by_context("k", ""); + assertGet($this); + assertUrl($this, "/resources/image/context"); + assertParam($this, "key", "k"); + assertNoParam($this, "value"); + + $this->api->resources_by_context("k", "v"); + assertGet($this); + assertUrl($this, "/resources/image/context"); + assertParam($this, "key", "k"); + assertParam($this, "value", "v"); + } + + /** + * Should allow getting resource metadata + * + * @throws Api\GeneralError + */ + public function test07_resource_metadata() + { + $resource = $this->api->resource(self::$api_test); + $this->assertNotEquals($resource, null); + $this->assertEquals($resource["public_id"], self::$api_test); + $this->assertEquals($resource["bytes"], LOGO_SIZE); + $this->assertEquals(count($resource["derived"]), 1); + } + + /** + * Should allow getting resource quality analysis + * + * @throws Api\GeneralError + */ + public function test_resource_quality_analysis() + { + $resource = $this->api->resource(self::$api_test, ["quality_analysis" => true]); + $this->assertArrayHasKey("quality_analysis", $resource); + $this->assertInternalType("double", $resource["quality_analysis"]["focus"]); + } + + /** + * Should allow cinemagraph_analysis parameter + * + * @throws Api\GeneralError + */ + public function test_resource_cinemagraph_analysis() + { + Curl::mockApi($this); + + $this->api->resource(self::$api_test, ["type" => "upload", "cinemagraph_analysis" => true]); + assertParam($this, "cinemagraph_analysis", 1); + } + + /** + * Should allow derived_next_cursor parameter + * + * @throws Api\GeneralError + */ + public function test_resource_derived_next_cursor() + { + Curl::mockApi($this); + + $this->api->resource(self::$api_test, ["derived_next_cursor" => "foo"]); + assertGet($this); + assertParam($this, "derived_next_cursor", "foo"); + } + /** + * Should allow deleting derived resource + * + * @throws Api\GeneralError + */ + public function test08_delete_derived() + { + $derived_resource_id = "foobar"; + Curl::mockApi($this); + $this->api->delete_derived_resources(array($derived_resource_id)); + assertDelete($this); + assertUrl($this, "/derived_resources"); + assertParam($this, "derived_resource_ids[0]", $derived_resource_id); + } + + /** + * Should allow deleting derived resources by transformation + * + * @throws Api\GeneralError + */ + public function test08a_delete_derived_by_transformation() + { + $public_id = "public_id"; + Curl::mockApi($this); + $this->api->delete_derived_by_transformation($public_id, self::$crop_transformation); + assertUrl($this, "/resources/image/upload"); + assertDelete($this); + assertParam($this, "keep_original", true); + assertParam($this, "public_ids[0]", $public_id); + assertParam($this, "transformations", self::$crop_transformation_str); + + $options = ["resource_type" => "raw", "type" => "fetch", "invalidate" => true]; + $this->api->delete_derived_by_transformation(array($public_id), self::$crop_transformation, $options); + assertDelete($this); + assertUrl($this, "/resources/raw/fetch"); + assertParam($this, "public_ids[0]", $public_id); + assertParam($this, "invalidate", true); + assertParam($this, "transformations", self::$crop_transformation_str); + + $this->api->delete_derived_by_transformation(array($public_id), self::$transformations); + assertDelete($this); + assertParam($this, "public_ids[0]", $public_id); + assertParam($this, "transformations", self::$transformations_str); + } + + /** + * Should allow deleting resources + * + * @throws Api\GeneralError + */ + public function test09_delete_resources() + { + Curl::mockApi($this); + $this->api->delete_resources( + array("apit_test", self::$api_test_2, self::$api_test_3) + ); + assertUrl($this, "/resources/image/upload"); + $this->assertEquals("DELETE", Curl::$instance->http_method(), "http method should be DELETE"); + assertParam($this, "public_ids[0]", "apit_test"); + } + + /** + * Should allow deleting resources by public id prefix + * + * @throws Api\GeneralError + */ + public function test09a_delete_resources_by_prefix() + { + Curl::mockApi($this); + $this->api->delete_resources_by_prefix("fooba"); + assertUrl($this, "/resources/image/upload"); + assertDelete($this); + assertParam($this, "prefix", "fooba"); + } + + /** + * Should allow deleting resources by tag + * + * @throws Api\GeneralError + */ + public function test09b_delete_resources_by_tag() + { + Curl::mockApi($this); + $this->api->delete_resources_by_tag("api_test_tag_for_delete"); + assertUrl($this, "/resources/image/tags/api_test_tag_for_delete"); + assertDelete($this); + } + + /** + * Should allow deleting resources by transformations + * + * @throws Api\GeneralError + */ + public function test09c_delete_resources_by_transformations() + { + Curl::mockApi($this); + $this->api->delete_resources(["api_test", "api_test2"], ["transformations" => self::$crop_transformation]); + $this->assertEquals("DELETE", Curl::$instance->http_method(), "http method should be DELETE"); + assertParam($this, "transformations", self::$crop_transformation_str); + + $this->api->delete_all_resources(["transformations" => self::$transformations]); + + $this->assertEquals("DELETE", Curl::$instance->http_method(), "http method should be DELETE"); + assertParam($this, "transformations", self::$transformations_str); + + $this->api->delete_resources_by_prefix("api_test_by", ["transformations" => self::$crop_transformation]); + $this->assertEquals("DELETE", Curl::$instance->http_method(), "http method should be DELETE"); + assertParam($this, "transformations", self::$crop_transformation_str); + + $this->api->delete_resources_by_tag("api_test_tag", ["transformations" => self::$crop_transformation]); + $this->assertEquals("DELETE", Curl::$instance->http_method(), "http method should be DELETE"); + assertParam($this, "transformations", self::$crop_transformation_str); + } + + /** + * Should allow listing tags + * + * @throws Api\GeneralError + */ + public function test10_tags() + { + Curl::mockApi($this); + $this->api->tags(); + assertUrl($this, "/tags/image"); + assertGet($this); + } + + /** + * Should allow listing tag by prefix + * + * @throws Api\GeneralError + */ + public function test11_tags_prefix() + { + Curl::mockApi($this); + $this->api->tags(array("prefix" => "fooba")); + assertUrl($this, "/tags/image"); + assertGet($this); + assertParam($this, "prefix", "fooba"); + } + + /** + * Should allow listing transformations + * + * @throws Api\GeneralError + */ + public function test12_transformations() + { + $result = $this->api->transformations(); + $this->assertArrayHasKey('transformations', $result); + $this->assertGreaterThan(0, count($result["transformations"])); + $transformation = $result["transformations"][0]; + + $this->assertNotNull($transformation); + $this->assertArrayHasKey('used', $transformation); + } + + /** + * Should allow listing of named transformations + * + * @throws Api\GeneralError + */ + public function test_transformations_named() + { + Curl::mockApi($this); + $this->api->transformations(array("named" => true)); + assertParam( + $this, + "named", + true, + "api->transformations should pass the named paramter" + ); + } + + /** + * Should allow listing transformations with cursor + * + * @throws Api\GeneralError + */ + public function test_transformation_cursor() + { + Curl::mockApi($this); + $this->api->transformation(self::$scale_transformation_str, array("next_cursor" => "234123132345")); + assertUrl($this, "/transformations"); + assertParam( + $this, + "next_cursor", + "234123132345", + "api->transformation should pass the next_cursor paramter" + ); + assertParam( + $this, + "transformation", + self::$scale_transformation_str + ); + } + + /** + * Should allow getting transformation cursor result + * + * @throws Api\GeneralError + */ + public function test_transformation_cursor_results() + { + Uploader::upload( + TEST_IMG, + array( + "public_id" => self::$api_test_4, + "eager" => array("transformation" => self::$scale_transformation) + ) + ); + + $result = $this->api->transformation(self::$scale_transformation_str, array("max_results" => 1)); + $this->assertEquals(count($result["derived"]), 1); + $this->assertNotEmpty($result["next_cursor"]); + + $result2 = $this->api->transformation( + self::$scale_transformation_str, + array("max_results" => 1, "next_cursor" => $result["next_cursor"]) + ); + $this->assertEquals(count($result2["derived"]), 1); + $this->assertNotEquals($result["derived"][0]["id"], $result2["derived"][0]["id"]); + } + + /** + * Should allow getting transformation metadata + * + * @throws Api\GeneralError + */ + public function test13_transformation_metadata() + { + $transformation = $this->api->transformation(self::$scale_transformation_str); + $this->assertNotEquals($transformation, null); + $this->assertEquals($transformation["info"], array(self::$scale_transformation)); + $transformation = $this->api->transformation(self::$scale_transformation); + $this->assertNotEquals($transformation, null); + $this->assertEquals($transformation["info"], array(self::$scale_transformation)); + } + + /** + * Should allow updating transformation allowed_for_strict + * + * @throws Api\GeneralError + */ + public function test14_transformation_update() + { + Curl::mockApi($this); + $this->api->update_transformation(self::$scale_transformation_str, array("allowed_for_strict" => true)); + assertUrl($this, "/transformations"); + assertPut($this); + assertParam($this, "allowed_for_strict", 1); + assertParam($this, "transformation", self::$scale_transformation_str); + } + + /** + * Should allow creating named transformation + * + * @throws Api\GeneralError + */ + public function test15_transformation_create() + { + Curl::mockApi($this); + $this->api->create_transformation(self::$api_test_transformation, self::$scale_transformation); + assertUrl($this, "/transformations"); + assertPost($this); + assertParam($this, "name", self::$api_test_transformation); + assertParam($this, "transformation", self::$scale_transformation_str); + } + + /** + * Should allow unsafe update of named transformation + * + * @throws Api\GeneralError + */ + public function test15a_transformation_unsafe_update() + { + $updated_transformation = array("crop" => "scale", "width" => 103); + + $this->api->create_transformation( + self::$api_test_transformation_3, + self::$scale_transformation + ); + $this->api->update_transformation( + self::$api_test_transformation_3, + array("unsafe_update" => $updated_transformation) + ); + $transformation = $this->api->transformation(self::$api_test_transformation_3); + $this->assertNotEquals($transformation, null); + $this->assertEquals($transformation["info"], array($updated_transformation)); + $this->assertEquals($transformation["used"], false); + } + + /** + * Should allow creating unnamed transformation with specified format + * + * @throws Api\GeneralError + */ + public function test15b_transformation_create_unnamed_with_format() + { + Curl::mockApi($this); + + $with_extension = self::$scale_transformation; + $with_extension["format"] = "jpg"; + $with_extension_str = self::$scale_transformation_str . "/jpg"; + $this->api->create_transformation($with_extension_str, $with_extension); + assertUrl($this, "/transformations"); + assertPost($this); + assertParam($this, "name", $with_extension_str); + assertParam($this, "transformation", $with_extension_str); + } + + /** + * Should allow creating unnamed extensionless transformation + * + * @throws Api\GeneralError + */ + public function test15c_transformation_create_unnamed_with_empty_format() + { + Curl::mockApi($this); + + $with_extension = self::$scale_transformation; + $with_extension["format"] = ""; + $with_extension_str = self::$scale_transformation_str . "/"; + $this->api->create_transformation($with_extension_str, $with_extension); + assertUrl($this, "/transformations"); + assertPost($this); + assertParam($this, "name", $with_extension_str); + assertParam($this, "transformation", $with_extension_str); + } + + /** + * Should allow deleting named transformation + * + * @throws Api\GeneralError + */ + public function test16a_transformation_delete() + { + + $this->api->create_transformation( + self::$api_test_transformation_2, + self::$scale_transformation + ); + $this->api->transformation(self::$api_test_transformation_2); + $this->api->delete_transformation(self::$api_test_transformation_2); + assertDelete($this); + } + + /** + * Should allow deleting implicit transformation + * + * @throws Api\GeneralError + */ + public function test17a_transformation_delete_implicit() + { + Curl::mockApi($this); + $this->api->delete_transformation(self::$scale_transformation_str); + assertUrl($this, "/transformations"); + assertParam($this, "transformation", self::$scale_transformation_str); + assertDelete($this); + } + + /** + * Should allow deleting and invalidating transformation + * + * @throws Api\GeneralError + */ + public function test_transformation_delete_with_invalidate() + { + Curl::mockApi($this); + + $transformation = self::$scale_transformation_str . ",a_90"; + $expected_url = '/transformations'; + + // should pass 'invalidate' param when 'invalidate' is set to true + $this->api->delete_transformation($transformation, array("invalidate" => true)); + assertUrl($this, $expected_url); + assertDelete($this); + assertParam($this, "invalidate", "1"); + assertParam($this, "transformation", self::$scale_transformation_str . ',a_90'); + + // should pass 'invalidate' param when 'invalidate' is set to false + $this->api->delete_transformation($transformation, array("invalidate" => false)); + assertUrl($this, $expected_url); + assertDelete($this); + assertParam($this, "invalidate", ""); + assertParam($this, "transformation", self::$scale_transformation_str . ',a_90'); + + // should not pass 'invalidate' param if not set + $this->api->delete_transformation($transformation); + assertUrl($this, $expected_url); + assertDelete($this); + assertNoParam($this, "invalidate"); + assertParam($this, "transformation", self::$scale_transformation_str . ',a_90'); + } + + /** + * Should allow listing resource_types + * + * @throws Api\GeneralError + */ + public function test18_usage() + { + $result = $this->api->usage(); + $this->assertNotEquals($result["last_updated"], null); + } + + /** + * Should allow deleting all resources + * + * @throws Api\GeneralError + */ + public function test19_delete_derived() + { + $this->markTestSkipped("Not enabled by default - remove this line to test"); + $options = array( + "public_id" => self::$api_test_5, + "eager" => array("transformation" => array("width" => 101, "crop" => "scale")), + ); + Uploader::upload(TEST_IMG, $options); + $resource = $this->api->resource(self::$api_test_5); + $this->assertNotEquals($resource, null); + $this->assertEquals(count($resource["derived"]), 1); + $this->api->delete_all_resources(array("keep_original" => true)); + $resource = $this->api->resource(self::$api_test_5); + $this->assertNotEquals($resource, null); + $this->assertEquals(count($resource["derived"]), 0); + } + + + /** + * Should support setting manual moderation status + * + * @throws Api\GeneralError + */ + public function test20_manual_moderation() + { + $resource = Uploader::upload(TEST_IMG, array("moderation" => "manual")); + $this->assertEquals($resource["moderation"][0]["status"], "pending"); + $this->assertEquals($resource["moderation"][0]["kind"], "manual"); + + $api_result = $this->api->update($resource["public_id"], array("moderation_status" => "approved")); + $this->assertEquals($api_result["moderation"][0]["status"], "approved"); + $this->assertEquals($api_result["moderation"][0]["kind"], "manual"); + } + + public function test21_notification_url() + { + Curl::mockApi($this); + $this->api->update("foobar", array("notification_url" => "http://example.com")); + assertParam($this, "notification_url", "http://example.com"); + } + + /** + * Should support requesting raw_convert + * + * @expectedException \Cloudinary\Api\BadRequest + * @expectedExceptionMessage Illegal value + * + * @throws Api\GeneralError + */ + public function test22_raw_conversion() + { + $resource = Uploader::upload(RAW_FILE, array("resource_type" => "raw")); + $this->api->update($resource["public_id"], array("raw_convert" => "illegal", "resource_type" => "raw")); + } + + /** + * Should support requesting categorization + * + * @expectedException \Cloudinary\Api\BadRequest + * @expectedExceptionMessage Illegal value + * + * @throws Api\GeneralError + */ + public function test23_categorization() + { + $this->api->update(self::$api_test, array("categorization" => "illegal")); + } + + /** + * Should support requesting detection + * + * @expectedException \Cloudinary\Api\BadRequest + * @expectedExceptionMessage Illegal value + * + * @throws Api\GeneralError + */ + public function test24_detection() + { + $this->api->update(self::$api_test, array("detection" => "illegal")); + } + + /** + * Should support requesting background_removal + * + * @expectedException \Cloudinary\Api\BadRequest + * @expectedExceptionMessage Illegal value + * + * @throws Api\GeneralError + */ + public function test25_background_removal() + { + $this->api->update(self::$api_test, array("background_removal" => "illegal")); + } + + /** + * Should support requesting auto_tagging + * + * @throws Api\GeneralError + */ + public function test26_auto_tagging() + { + Curl::mockApi($this); + $this->api->update("foobar", array("auto_tagging" => 0.5)); + assertUrl($this, "/resources/image/upload/foobar"); + assertPost($this); + assertParam($this, "auto_tagging", 0.5); + } + + /** + * Should support updating ocr + * + * @throws Api\GeneralError + */ + public function test26_1_ocr() + { + Curl::mockApi($this); + $this->api->update("foobar", array("ocr" => "adv_ocr")); + assertParam($this, "ocr", "adv_ocr"); + } + + /** + * Should support updating quality override + * + * @throws Api\GeneralError + */ + public function test26_2_quality_override() + { + Curl::mockApi($this); + $values = ['auto:advanced', 'auto:best', '80:420', 'none']; + foreach ($values as $value) { + $this->api->update("api_test", array("quality_override" => $value)); + assertParam($this, "quality_override", $value); + } + } + + /** + * Should allow listing resources by start date + * + * @throws Api\GeneralError + */ + public function test27_start_at() + { + Curl::mockApi($this); + $dateTime = new \DateTime(); + $start_at = $dateTime->format(\DateTime::ISO8601); + $this->api->resources(array("type" => "upload", "start_at" => $start_at, "direction" => "asc")); + assertUrl($this, "/resources/image/upload"); + assertParam($this, "start_at", $start_at); + assertParam($this, "direction", "asc"); + } + + /** + * Should allow creating upload_presets + * + * @throws Api\GeneralError + */ + public function test28_create_upload_presets() + { + Curl::mockApi($this); + $this->api->create_upload_preset(array("name" => TEST_PRESET_NAME, "folder" => "folder", "live" => true)); + assertUrl($this, "/upload_presets"); + assertPost($this); + assertParam($this, "name", TEST_PRESET_NAME); + assertParam($this, "folder", "folder"); + assertParam($this, "live", 1); + } + + /** + * Should allow listing upload_presets + * + * @throws Api\GeneralError + */ + public function test28a_list_upload_presets() + { + Curl::mockApi($this); + $this->api->upload_presets(); + assertUrl($this, "/upload_presets"); + assertGet($this); + } + + /** + * Should allow getting a single upload_preset + * + * @throws Api\GeneralError + */ + public function test29_get_upload_presets() + { + Curl::mockApi($this); + $this->api->upload_preset(TEST_PRESET_NAME); + assertUrl($this, "/upload_presets/" . TEST_PRESET_NAME); + assertGet($this); + } + + /** + * Should allow deleting upload_presets + * + * @throws Api\GeneralError + */ + public function test30_delete_upload_presets() + { + Curl::mockApi($this); + $this->api->delete_upload_preset(TEST_PRESET_NAME); + assertUrl($this, "/upload_presets/" . TEST_PRESET_NAME); + assertDelete($this); + } + + /** + * Should allow updating upload_presets + * + * @throws Api\GeneralError + */ + public function test31_update_upload_presets() + { + Curl::mockApi($this); + $this->api->update_upload_preset( + TEST_PRESET_NAME, + array("colors" => true, "unsigned" => true, "disallow_public_id" => true, "live" => true) + ); + assertPut($this); + assertUrl($this, "/upload_presets/" . TEST_PRESET_NAME); + assertParam($this, "colors", 1); + assertParam($this, "unsigned", 1); + assertParam($this, "disallow_public_id", 1); + assertParam($this, "live", 1); + } + + /** + * Should allow listing folder + * + * @throws Api\GeneralError + */ + public function test32_folder_listing() + { + $this->markTestSkipped("For this test to work, 'Auto-create folders' should be enabled" . + "in the Upload Settings, and the account should be empty of folders. " . + "Comment out this line if you really want to test it."); + + Uploader::upload(TEST_IMG, array("public_id" => "test_folder1/item")); + Uploader::upload(TEST_IMG, array("public_id" => "test_folder2/item")); + Uploader::upload(TEST_IMG, array("public_id" => "test_folder1/test_subfolder1/item")); + Uploader::upload(TEST_IMG, array("public_id" => "test_folder1/test_subfolder2/item")); + $result = $this->api->root_folders(); + $this->assertContains(array("name" => "test_folder1", "path" => "test_folder1"), $result["folders"]); + $this->assertContains(array("name" => "test_folder2", "path" => "test_folder2"), $result["folders"]); + $result = $this->api->subfolders("test_folder1"); + $this->assertContains( + array("name" => "test_subfolder1", "path" => "test_folder1/test_subfolder1"), + $result["folders"] + ); + $this->assertContains( + array("name" => "test_subfolder2", "path" => "test_folder1/test_subfolder2"), + $result["folders"] + ); + } + + /** + * Should allow max_results and next_cursor in root_folders and subfolders + * + * @throws Api\GeneralError + */ + public function test_root_folder_and_subfolders_allow_max_results_and_next_cursor() + { + Curl::mockApi($this); + $next_cursor = '72410bbc4bfa1a135d9df56d91c072ba3356570d333450b286'. + 'aec30af27dbe3b6b51054047a65b007c8363900c3fe6ae'; + + $this->api->root_folders([ + 'max_results' => 3, + 'next_cursor' => $next_cursor, + ]); + + assertGet($this); + assertParam($this, "max_results", 3); + assertParam($this, "next_cursor", $next_cursor); + + $this->api->subfolders('folder1', [ + 'max_results' => 3, + 'next_cursor' => $next_cursor, + ]); + + assertGet($this); + assertParam($this, "max_results", 3); + assertParam($this, "next_cursor", $next_cursor); + } + + /** + * Should throw exception on non-existing folder + * + * @expectedException \Cloudinary\Api\NotFound + * + * @throws Api\GeneralError + */ + public function test33_folder_listing_error() + { + $this->api->subfolders("I-do-not-exist"); + } + + /** + * Should create folder + * + * @throws Api\GeneralError + * @throws Exception + */ + public function test_create_folder() + { + $folderPath = 'folder7'; + + Curl::mockApi($this); + + $this->api->create_folder($folderPath); + + assertPost($this); + assertUrl($this, "/folders/$folderPath"); + } + + /** + * Should delete folder + * + * @throws Api\GeneralError + * @throws Exception + */ + public function test_delete_folder() + { + $folderPath = UNIQUE_TEST_FOLDER; + + Curl::mockApi($this); + + $this->api->delete_folder($folderPath); + + assertDelete($this); + assertUrl($this, "/folders/$folderPath"); + } + + /** + * Should allow restoring resources + * + * @throws Api\GeneralError + */ + public function test34_restore() + { + Curl::mockApi($this); + $this->api->restore(array("api_test_restore")); + assertPost($this); + assertUrl($this, "/resources/image/upload/restore"); + assertParam($this, "public_ids[0]", "api_test_restore"); + } + + /** + * Should allow upload mapping + * @throws Api\GeneralError + */ + public function test35_upload_mapping() + { + Curl::mockApi($this); + + $this->api->create_upload_mapping("api_test_upload_mapping", array("template" => "http://cloudinary.com")); + assertUrl($this, "/upload_mappings"); + assertPost($this); + assertParam($this, "folder", "api_test_upload_mapping"); + assertParam($this, "template", "http://cloudinary.com"); + + $this->api->upload_mapping("api_test_upload_mapping"); + assertUrl($this, "/upload_mappings"); + assertGet($this); + assertParam($this, "folder", "api_test_upload_mapping"); + + $this->api->update_upload_mapping( + "api_test_upload_mapping", + array("template" => "http://res.cloudinary.com") + ); + assertUrl($this, "/upload_mappings"); + assertPut($this); + assertParam($this, "folder", "api_test_upload_mapping"); + assertParam($this, "template", "http://res.cloudinary.com"); + + $this->api->delete_upload_mapping("api_test_upload_mapping"); + assertUrl($this, "/upload_mappings"); + assertDelete($this); + assertParam($this, "folder", "api_test_upload_mapping"); + } + + private static $predefined_profiles = array( + "4k", + "full_hd", + "hd", + "sd", + "full_hd_wifi", + "full_hd_lean", + "hd_lean", + ); + + /** + * Should allow creating streaming profile + * + * @throws Api\GeneralError + */ + public function test_create_streaming_profile() + { + $options = array( + "representations" => array( + array( + "transformation" => array( + "bit_rate" => "5m", + "height" => 1200, + "width" => 1200, + "crop" => "limit", + ), + ), + ), + ); + $result = $this->api->create_streaming_profile(self::$streaming_profile_1, $options); + $this->assertArrayHasKey("representations", $result["data"]); + $reps = $result["data"]["representations"]; + $this->assertTrue(is_array($reps)); + // transformation is returned as an array + $this->assertTrue(is_array($reps[0]["transformation"])); + + $tr = $reps[0]["transformation"][0]; + $expected = array("bit_rate" => "5m", "height" => 1200, "width" => 1200, "crop" => "limit"); + $this->assertEquals($expected, $tr); + } + + /** + * Should allow updating and deleting streaming profile + * + * @throws Api\GeneralError + */ + public function test_update_delete_streaming_profile() + { + $options = array( + "representations" => array( + array( + "transformation" => array( + "bit_rate" => "5m", + "height" => 1200, + "width" => 1200, + "crop" => "limit", + ), + ), + ), + ); + try { + $this->api->create_streaming_profile(self::$streaming_profile_2, $options); + } catch (Cloudinary\Api\AlreadyExists $e) { + } + + $options = array( + "representations" => array( + array( + "transformation" => array( + "bit_rate" => "5m", + "height" => 1000, + "width" => 1000, + "crop" => "scale", + ), + ), + ), + ); + $result = $this->api->update_streaming_profile(self::$streaming_profile_2, $options); + + $this->assertArrayHasKey("representations", $result["data"]); + $reps = $result["data"]["representations"]; + $this->assertTrue(is_array($reps)); + // transformation is returned as an array + $this->assertTrue(is_array($reps[0]["transformation"])); + + $tr = $reps[0]["transformation"][0]; + $expected = array("bit_rate" => "5m", "height" => 1000, "width" => 1000, "crop" => "scale"); + $this->assertEquals($expected, $tr); + + $this->api->delete_streaming_profile(self::$streaming_profile_2); + $result = $this->api->list_streaming_profiles(); + $this->assertArrayNotHasKey(self::$streaming_profile_2, array_map(function ($profile) { + return $profile["name"]; + }, $result["data"])); + } + + /** + * Should allow getting streaming profile + * + * @throws Api\GeneralError + */ + public function test_get_streaming_profile() + { + + $result = $this->api->get_streaming_profile(self::$predefined_profiles[0]); + $this->assertArrayHasKey("representations", $result["data"]); + $reps = $result["data"]["representations"]; + $this->assertTrue(is_array($reps)); + // transformation is returned as an array + $this->assertTrue(is_array($reps[0]["transformation"])); + + $tr = $reps[0]["transformation"][0]; + $this->assertArrayHasKey("bit_rate", $tr); + $this->assertArrayHasKey("height", $tr); + $this->assertArrayHasKey("width", $tr); + $this->assertArrayHasKey("crop", $tr); + } + + /** + * Should allow listing streaming profiles + * + * @throws Api\GeneralError + */ + public function test_list_streaming_profile() + { + $result = $this->api->list_streaming_profiles(); + $names = array_map(function ($profile) { + return $profile["name"]; + }, $result["data"]); + $this->assertEmpty(array_diff(self::$predefined_profiles, $names)); + } + + /** + * Should allow updating resources + * + * @throws Api\GeneralError + */ + public function test_update_parameters() + { + Curl::mockApi($this); + + $this->api->update(self::$api_test, array("auto_tagging" => 0.5)); + assertUrl($this, '/resources/image/upload/' . self::$api_test); + assertParam($this, "auto_tagging", 0.5); + $fields = Curl::$instance->fields(); + $this->assertArrayNotHasKey("face_coordinates", $fields, "update() should not send empty parameters"); + $this->assertArrayNotHasKey("tags", $fields, "update() should not send empty parameters"); + $this->assertArrayNotHasKey("context", $fields, "update() should not send empty parameters"); + $this->assertArrayNotHasKey("face_coordinates", $fields, "update() should not send empty parameters"); + $this->assertArrayNotHasKey("custom_coordinates", $fields, "update() should not send empty parameters"); + $this->assertArrayNotHasKey("access_control", $fields, "update() should not send empty parameters"); + } + + /** + * Should allow the user to define ACL in the update parameters + * + * @throws Api\GeneralError + */ + public function test_update_access_control() + { + Curl::mockApi($this); + + $acl = array("access_type" => "anonymous", + "start" => "2018-02-22 16:20:57 +0200", + "end"=> "2018-03-22 00:00 +0200" + ); + $exp_acl = '[{"access_type":"anonymous",' . + '"start":"2018-02-22 16:20:57 +0200",' . + '"end":"2018-03-22 00:00 +0200"}]'; + + $this->api->update(self::$api_test, array("access_control" => $acl)); + + assertParam($this, "access_control", $exp_acl); + } + + /** + * Should correctly encode url + * + * @throws Api\GeneralError + */ + public function test_url_encoding() + { + Curl::mockApi($this); + + $public_ids = array("with space", "special!@#$%^&*(){}|?characters"); + $expected_public_ids = array("with%20space", "special%21%40%23%24%25%5E%26%2A%28%29%7B%7D%7C%3Fcharacters"); + + foreach ($public_ids as $index => $public_id) { + $this->api->update($public_id); + assertUrl($this, "/resources/image/upload/" . $expected_public_ids[$index]); + } + + $tags = array("tag1,tag2", 'with space,another space', 'spec!@l_t@g$%^&*_)(*>?<>||}{'); + $expected_tags = array( + "tag1%2Ctag2", + 'with%20space%2Canother%20space', + 'spec%21%40l_t%40g%24%25%5E%26%2A_%29%28%2A%3E%3F%3C%3E%7C%7C%7D%7B'); + + foreach ($tags as $index => $tag) { + $this->api->resources_by_tag($tag); + assertUrl($this, "/resources/image/tags/" . $expected_tags[$index]); + } + } + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/ArchiveTest.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/ArchiveTest.php new file mode 100644 index 0000000..6865bdf --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/ArchiveTest.php @@ -0,0 +1,112 @@ +tag = "archive_test_" . SUFFIX; + $this->tags = array($this->tag, TEST_TAG, UNIQUE_TEST_TAG); + \Cloudinary::reset_config(); + if (!\Cloudinary::config_get("api_secret")) { + $this->markTestSkipped('Please setup environment for Upload test to run'); + } + + Uploader::upload("tests/logo.png", array("tags" => $this->tags)); + Uploader::upload("tests/logo.png", array("tags" => $this->tags, "width" => 10, "crop" => "scale")); + } + + public function tearDown() + { + Curl::$instance = new Curl(); + $api = new \Cloudinary\Api(); + $api->delete_resources_by_tag($this->tag); + } + + public function test_create_zip() + { + $result = Uploader::create_zip(array("tags" => $this->tag)); + $this->assertEquals(2, $result["file_count"]); + } + + public function test_expires_at() + { + Uploader::create_zip(array("tags" => $this->tag, "expires_at" => time() + 3600)); + assertUrl($this, '/image/generate_archive'); + assertParam($this, "target_format", "zip"); + assertParam($this, "tags[0]", $this->tag); + assertParam($this, "expires_at", null, "should support the 'expires_at' parameter"); + } + + public function test_skip_transformation_name() + { + Curl::mockUpload($this); + Uploader::create_zip(array("tags" => $this->tag, "skip_transformation_name" => true)); + assertUrl($this, '/image/generate_archive'); + assertParam($this, "tags[0]", $this->tag); + assertParam( + $this, + "skip_transformation_name", + 1, + "should support the 'skip_transformation_name' parameter" + ); + } + + public function test_allow_missing() + { + Curl::mockUpload($this); + Uploader::create_zip(array("tags" => $this->tag, "allow_missing" => true)); + assertUrl($this, '/image/generate_archive'); + assertParam($this, "tags[0]", $this->tag); + assertParam($this, "allow_missing", 1, "should support the 'allow_missing' parameter"); + } + + public function test_download_zip_url() + { + $result = \Cloudinary::download_zip_url(array("tags" => $this->tag)); + $file = tempnam(".", "zip"); + file_put_contents($file, file_get_contents($result)); + $zip = new \ZipArchive(); + $zip->open($file); + $this->assertEquals(2, $zip->numFiles); + unlink($file); + } + + public function test_create_archive_multiple_resource_types() + { + Curl::mockUpload($this); + + $testIds = [ + "image/upload/" . UNIQUE_TEST_ID, + "video/upload/" . UNIQUE_TEST_ID, + "raw/upload/" . UNIQUE_TEST_ID, + ]; + + Uploader::create_zip( + array( + "resource_type" => "auto", + "fully_qualified_public_ids" => $testIds + ) + ); + + assertUrl($this, '/auto/generate_archive'); + + assertParam($this, "fully_qualified_public_ids[0]", $testIds[0]); + assertParam($this, "fully_qualified_public_ids[1]", $testIds[1]); + assertParam($this, "fully_qualified_public_ids[2]", $testIds[2]); + } + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/AuthTokenTest.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/AuthTokenTest.php new file mode 100644 index 0000000..3c6581a --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/AuthTokenTest.php @@ -0,0 +1,193 @@ +url_backup = getenv("CLOUDINARY_URL"); + \Cloudinary::config_from_url("cloudinary://a:b@test123?auth_token[duration]=300&auth_token[start_time]=11111111&auth_token[key]=" . AuthTokenTest::KEY); + \Cloudinary::config(array("private_cdn" => true)); + } + + protected function tearDown() + { + parent::tearDown(); + putenv("CLOUDINARY_URL=" . $this->url_backup); + \Cloudinary::config_from_url($this->url_backup); + } + + public function test_generate_with_start_time_and_duration() + { + $message = "should generate with start and duration"; + $token = \Cloudinary::generate_auth_token(array( + "start_time" => 1111111111, + "acl" => "/image/*", + "duration" => 300, + )); + $this->assertEquals( + '__cld_token__=st=1111111111~exp=1111111411~acl=%2fimage%2f*~hmac=1751370bcc6cfe9e03f30dd1a9722ba0f2cdca283fa3e6df3342a00a7528cc51', + $token, + $message + ); + } + + public function test_should_add_token_if_authToken_is_globally_set_and_signed_is_True() + { + $message = "should add token if authToken is globally set and signed = true"; + $options = array( + "sign_url" => true, + "resource_type" => "image", + "type" => "authenticated", + "version" => "1486020273", + ); + $url = \Cloudinary::cloudinary_url("sample.jpg", $options); + $this->assertEquals( + "http://test123-res.cloudinary.com/image/authenticated/v1486020273/sample.jpg?__cld_token__=st=11111111~exp=11111411~hmac=8db0d753ee7bbb9e2eaf8698ca3797436ba4c20e31f44527e43b6a6e995cfdb3", + $url, + $message + ); + } + + public function test_should_add_token_for_public_resource() + { + $message = "should add token for 'public' resource"; + $options = array( + "sign_url" => true, + "resource_type" => "image", + "type" => "public", + "version" => "1486020273", + ); + $url = \Cloudinary::cloudinary_url("sample.jpg", $options); + $this->assertEquals( + "http://test123-res.cloudinary.com/image/public/v1486020273/sample.jpg?__cld_token__=st=11111111~exp=11111411~hmac=c2b77d9f81be6d89b5d0ebc67b671557e88a40bcf03dd4a6997ff4b994ceb80e", + $url, + $message + ); + } + + public function test_should_not_add_token_if_signed_is_false() + { + $message = "should not add token if signed is null"; + $options = array("type" => "authenticated", "version" => "1486020273"); + $url = \Cloudinary::cloudinary_url("sample.jpg", $options); + $this->assertEquals( + "http://test123-res.cloudinary.com/image/authenticated/v1486020273/sample.jpg", $url, + $message + ); + } + + public function test_null_token() + { + $message = "should not add token if authToken is globally set but null auth token is explicitly set and signed = true"; + $options = array( + "auth_token" => false, + "sign_url" => true, + "type" => "authenticated", + "version" => "1486020273", + ); + $url = \Cloudinary::cloudinary_url("sample.jpg", $options); + $this->assertEquals( + "http://test123-res.cloudinary.com/image/authenticated/s--v2fTPYTu--/v1486020273/sample.jpg", + $url, + $message + ); + } + + public function test_explicit_authToken_should_override_global_setting() + { + $message = "explicit authToken should override global setting"; + $options = array( + "sign_url" => true, + "auth_token" => array( + "key" => AuthTokenTest::ALT_KEY, + "start_time" => 222222222, + "duration" => 100, + ), + "type" => "authenticated", + "transformation" => array( + "crop" => "scale", + "width" => 300, + ), + ); + $url = \Cloudinary::cloudinary_url("sample.jpg", $options); + $this->assertEquals( + "http://test123-res.cloudinary.com/image/authenticated/c_scale,w_300/sample.jpg?__cld_token__=st=222222222~exp=222222322~hmac=55cfe516530461213fe3b3606014533b1eca8ff60aeab79d1bb84c9322eebc1f", + $url, + $message + ); + } + + public function test_should_compute_expiration_as_start_time_plus_duration() + { + $message = "should compute expiration as start time + duration"; + $token = array("key" => AuthTokenTest::KEY, "start_time" => 11111111, "duration" => 300); + $options = array( + "sign_url" => true, + "auth_token" => $token, + "resource_type" => "image", + "type" => "authenticated", + "version" => "1486020273", + ); + $url = \Cloudinary::cloudinary_url("sample.jpg", $options); + $this->assertEquals( + "http://test123-res.cloudinary.com/image/authenticated/v1486020273/sample.jpg?__cld_token__=st=11111111~exp=11111411~hmac=8db0d753ee7bbb9e2eaf8698ca3797436ba4c20e31f44527e43b6a6e995cfdb3", + $url, + $message + ); + } + + /** + * @expectedException \Cloudinary\Error + */ + public function test_must_provide_expiration_or_duration() + { + + $message = "should throw if expiration and duration are not provided"; + $token = array("key" => AuthTokenTest::KEY, "expiration" => null, "duration" => null); + AuthToken::generate($token); + $this->fail($message); + } + + /** + * @throws \Cloudinary\Error + */ + public function test_should_ignore_url_if_acl_is_provided() + { + $acl_opts = ["key" => AuthTokenTest::KEY, "start_time" => 1111111111, "duration" => 300, "acl" => "/image/*"]; + $acl_token = AuthToken::generate($acl_opts); + + $acl_opts["url"] = "sample.jpg"; + + $acl_token_url_ignored = AuthToken::generate($acl_opts); + + $this->assertEquals( + $acl_token, + $acl_token_url_ignored + ); + } + + /** + * @throws ReflectionException + */ + public function test_escape_to_lower() + { + $method = new ReflectionMethod('Cloudinary\\AuthToken', 'escape_to_lower'); + $method->setAccessible(true); + + $this->assertEquals( + 'Encode%20these%20%3a%7e%40%23%25%5e%26%7b%7d%5b%5d%5c%22%27%3b%2f%22,%20but%20not%20those%20$!()_.*', + $method->invoke(null, 'Encode these :~@#%^&{}[]\\"\';/", but not those $!()_.*') + ); + } +} + diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/Cache/Adapter/KeyValueCacheAdapterTest.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/Cache/Adapter/KeyValueCacheAdapterTest.php new file mode 100644 index 0000000..6832f42 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/Cache/Adapter/KeyValueCacheAdapterTest.php @@ -0,0 +1,155 @@ +storage = new DummyCacheStorage(); + $this->adapter = new KeyValueCacheAdapter($this->storage); + } + + /** + * Should be successfully initialized with a valid storage + */ + public function testInitialization() + { + $validStorage = new DummyCacheStorage(); + $validAdapter = new KeyValueCacheAdapter($validStorage); + Assert::assertAttributeEquals($validStorage, 'keyValueStorage', $validAdapter); + } + + /** + * Data provider of invalid storage types for testInvalidInitialization + * + * @return array of invalid storage types + */ + public function invalidStorageProvider() + { + return [ + [null], + ['notAStorage'], + [''], + [5375], + [[]], + [true], + [new \StdClass] + ]; + } + + /** + * Should throw InvalidArgumentException in case of initialization with an invalid storage + * + * @dataProvider invalidStorageProvider + * + * @param mixed $invalidStorage Invalid storage type provided by invalidStorages data provider + * + * @expectedException InvalidArgumentException + */ + public function testInvalidInitialization($invalidStorage) + { + new KeyValueCacheAdapter($invalidStorage); // Boom! + } + + public function testGenerateCacheKey() + { + list($publicId, $type, $resourceType, $transformation, $format) = $this->parameters; + + $values = [ + [ // valid values + "467d06e5a695b15468f9362e5a58d44de523026b", + $this->parameters + ], + [ // allow empty values + "1576396c59fc50ac8dc37b75e1184268882c9bc2", + [$publicId, $type, $resourceType, "", null] + ], + [ // allow empty values + "d8d824ca4e9ac735544ff3c45c1df67749cc1520", + [$publicId, false, [], $transformation, $format] + ] + ]; + foreach ($values as $v) { + $p = $v[1]; + // Unfortunately no `...` in PHP before 5.6, so just pass all parameters one by one + Assert::assertEquals($v[0], $this->adapter->generateCacheKey($p[0], $p[1], $p[2], $p[3], $p[4])); + } + } + + public function testGetSet() + { + list($publicId, $type, $resourceType, $transformation, $format) = $this->parameters; + $value = $this->value; + + $this->adapter->set($publicId, $type, $resourceType, $transformation, $format, $value); + $actual_value = $this->adapter->get($publicId, $type, $resourceType, $transformation, $format); + + Assert::assertEquals($value, $actual_value); + } + + public function testDelete() + { + list($publicId, $type, $resourceType, $transformation, $format) = $this->parameters; + + $this->adapter->set($publicId, $type, $resourceType, $transformation, $format, $this->value); + $actual_value = $this->adapter->get($publicId, $type, $resourceType, $transformation, $format); + + Assert::assertEquals($this->value, $actual_value); + + $this->adapter->delete($publicId, $type, $resourceType, $transformation, $format); + $deleted_value = $this->adapter->get($publicId, $type, $resourceType, $transformation, $format); + + Assert::assertNull($deleted_value); + + // delete non-existing key + $result = $this->adapter->delete($publicId, $type, $resourceType, $transformation, $format); + Assert::assertTrue($result); + } + + public function testFlushAll() + { + list($publicId, $type, $resourceType, $transformation, $format) = $this->parameters; + $value = $this->value; + + list($publicId2, $type2, $resourceType2, $transformation2, $format2) = $this->parameters2; + $value2 = $this->value2; + + $this->adapter->set($publicId, $type, $resourceType, $transformation, $format, $value); + $this->adapter->set($publicId2, $type2, $resourceType2, $transformation2, $format2, $value2); + + $actual_value = $this->adapter->get($publicId, $type, $resourceType, $transformation, $format); + $actual_value2 = $this->adapter->get($publicId2, $type2, $resourceType2, $transformation2, $format2); + + Assert::assertEquals($value, $actual_value); + Assert::assertEquals($value2, $actual_value2); + + $this->adapter->flushAll(); + + $deleted_value = $this->adapter->get($publicId, $type, $resourceType, $transformation, $format); + $deleted_value2 = $this->adapter->get($publicId2, $type2, $resourceType2, $transformation2, $format2); + + Assert::assertNull($deleted_value); + Assert::assertNull($deleted_value2); + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/Cache/ResponsiveBreakpointsCacheTest.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/Cache/ResponsiveBreakpointsCacheTest.php new file mode 100644 index 0000000..66706ab --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/Cache/ResponsiveBreakpointsCacheTest.php @@ -0,0 +1,116 @@ +cache = ResponsiveBreakpointsCache::instance(); + $this->cache->setCacheAdapter(new KeyValueCacheAdapter(new DummyCacheStorage())); + } + + public function testRBCacheInstance() + { + $instance = ResponsiveBreakpointsCache::instance(); + $instance2 = ResponsiveBreakpointsCache::instance(); + + $this::assertEquals($instance, $instance2); + } + + public function testRBCacheSetGet() + { + $this->cache->set(self::$publicId, [], self::$breakpoints); + + $res = $this->cache->get(self::$publicId); + + $this::assertEquals(self::$breakpoints, $res); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testRBCacheSetInvalidBreakpoints() + { + $this->cache->set(self::$publicId, [], "Not breakpoints at all"); + } + + public function testRBCacheDelete() + { + $this->cache->set(self::$publicId, [], self::$breakpoints); + + $this->cache->delete(self::$publicId); + + $res = $this->cache->get(self::$publicId); + + $this::assertNull($res); + } + + + public function testRBCacheFlushAll() + { + $this->cache->set(self::$publicId, [], self::$breakpoints); + + $this->cache->flushAll(); + + Assert::assertNull($this->cache->get(self::$publicId)); + } + + public function testRBCacheDisabled() + { + $disabledCache = ResponsiveBreakpointsCache::instance(); + + $disabledCacheReflection = new \ReflectionClass($disabledCache); + + $cacheAdapterProperty = $disabledCacheReflection->getProperty('cacheAdapter'); + $cacheAdapterProperty->setAccessible(true); + $cacheAdapterProperty->setValue($disabledCache, null); + + Assert::assertFalse($disabledCache->enabled()); + + Assert::assertFalse($disabledCache->set(self::$publicId, [], self::$breakpoints)); + Assert::assertNull($disabledCache->get(self::$publicId)); + Assert::assertFalse($disabledCache->delete(self::$publicId)); + Assert::assertFalse($disabledCache->flushAll()); + } + + public function testRBCacheFileSystemStorage() + { + $this->cache->init(["cache_adapter" => new KeyValueCacheAdapter(new FileSystemKeyValueStorage(null))]); + + try { + $this->cache->set(self::$publicId, [], self::$breakpoints); + $res = $this->cache->get(self::$publicId); + } catch (Exception $e) { + unset($e); + } + // No `finally` in PHP 5.4 + $this->cache->delete(self::$publicId); + + $this::assertEquals(self::$breakpoints, $res); + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/Cache/Storage/DummyCacheStorage.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/Cache/Storage/DummyCacheStorage.php new file mode 100644 index 0000000..bd33b8d --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/Cache/Storage/DummyCacheStorage.php @@ -0,0 +1,59 @@ +rootPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(UNIQUE_TEST_ID . '_'); + mkdir($this->rootPath); + + $this->storage = new FileSystemKeyValueStorage($this->rootPath); + } + + public function tearDown() + { + self::rmTree($this->rootPath); + } + + /** + * Helper Method, removes directory recursively + * + * @param string $dir Directory to remove + * + * @return bool + */ + public static function rmTree($dir) + { + $files = array_diff(scandir($dir), array('.','..')); + foreach ($files as $file) { + (is_dir("$dir/$file")) ? self::rmTree("$dir/$file") : unlink("$dir/$file"); + } + return rmdir($dir); + } + + /** + * Helper method for getting key full path + * + * @param $key + * + * @return mixed key full path + */ + private function getTestPath($key) + { + $getKeyFullPath = new ReflectionMethod('Cloudinary\Cache\Storage\FileSystemKeyValueStorage', 'getKeyFullPath'); + $getKeyFullPath->setAccessible(true); + + return $getKeyFullPath->invoke($this->storage, $key); + } + + + /** + * Helper method for setting value for the key + * + * @param $key + * @param $value + */ + private function setTestValue($key, $value) + { + $bytesWritten = file_put_contents($this->getTestPath($key), $value); + + self::assertNotFalse($bytesWritten); + } + + /** + * Helper method for getting value of the key + * + * @param $key + * + * @return bool|string value + */ + private function getTestValue($key) + { + return file_get_contents($this->getTestPath($key)); + } + + public function testInitWithNonExistingPath() + { + $nonExistingRootPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(UNIQUE_TEST_ID . '_'); + + $success = true; + + try { + $this->storage = new FileSystemKeyValueStorage($nonExistingRootPath); + } catch (\Exception $e) { + $success = false; + } + + Assert::assertFileExists($nonExistingRootPath); // works with directories too + + self::rmTree($nonExistingRootPath); + + Assert::assertTrue($success); + } + + public function testSet() + { + $this->storage->set($this->key, $this->value); + + Assert::assertEquals($this->value, $this->getTestValue($this->key)); + + // Should set empty value + $this->storage->set($this->key, ''); + + Assert::assertEquals("", $this->getTestValue($this->key)); + } + + public function testGet() + { + $this->setTestValue($this->key, $this->value); + + Assert::assertEquals($this->value, $this->storage->get($this->key)); + + Assert::assertNull($this->storage->get('non-existing-key')); + + $this->setTestValue($this->key, ''); + + Assert::assertEquals('', $this->storage->get($this->key)); + } + + public function testDelete() + { + $this->storage->set($this->key, $this->value); + $this->storage->set($this->key2, $this->value2); + + Assert::assertEquals($this->value, $this->storage->get($this->key)); + Assert::assertEquals($this->value2, $this->storage->get($this->key2)); + + Assert::assertTrue($this->storage->delete($this->key)); + Assert::assertNull($this->storage->get($this->key)); + + // Should delete only one value (opposed to clear) + Assert::assertEquals($this->value2, $this->storage->get($this->key2)); + + // Should not crash on non-existing keys + Assert::assertTrue($this->storage->delete($this->key)); + } + + public function testClear() + { + $this->storage->set($this->key, $this->value); + $this->storage->set($this->key2, $this->value2); + + Assert::assertEquals($this->value, $this->storage->get($this->key)); + Assert::assertEquals($this->value2, $this->storage->get($this->key2)); + + Assert::assertTrue($this->storage->clear()); + + Assert::assertNull($this->storage->get($this->key)); + Assert::assertNull($this->storage->get($this->key2)); + // Should clear empty cache + Assert::assertTrue($this->storage->clear()); + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/CloudinaryFieldTest.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/CloudinaryFieldTest.php new file mode 100644 index 0000000..d9f7d8c --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/CloudinaryFieldTest.php @@ -0,0 +1,40 @@ + "test123", + "secure_distribution" => null, + "private_cdn" => false, + "cname" => null)); + } + + public function testCloudinaryUrlFromCloudinaryField() + { + // [/][/][v/][.][#] + + // should use cloud_name from config + $result = Cloudinary::cloudinary_url(new CloudinaryField('test')); + $this->assertEquals('http://res.cloudinary.com/test123/image/upload/test', $result); + + // should ignore signature + $result = Cloudinary::cloudinary_url(new CloudinaryField('test#signature')); + $this->assertEquals('http://res.cloudinary.com/test123/image/upload/test', $result); + + $result = Cloudinary::cloudinary_url(new CloudinaryField('rss/imgt/v123/test.jpg')); + $this->assertEquals('http://res.cloudinary.com/test123/rss/imgt/v123/test.jpg', $result); + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/CloudinaryTest.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/CloudinaryTest.php new file mode 100644 index 0000000..1c6f94a --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/CloudinaryTest.php @@ -0,0 +1,1913 @@ + 'crop', 'width' => 100]; + protected static $crop_transformation_str = 'c_crop,w_100'; + protected static $raw_transformation = "c_fill,e_grayscale,q_auto"; + protected static $sepia_transformation = ['crop' => 'lfill', 'width' => 400, 'effect' => 'sepia']; + protected static $sepia_transformation_str = 'c_lfill,e_sepia,w_400'; + + private static $custom_function_wasm = ['function_type' => 'wasm', 'source' => 'blur.wasm']; + private static $custom_function_wasm_str = 'wasm:blur.wasm'; + + private static $custom_function_remote = [ + 'function_type' => 'remote', + 'source' => 'https://df34ra4a.execute-api.us-west-2.amazonaws.com/default/cloudinaryFn', + ]; + private static $custom_function_remote_str = + 'remote:aHR0cHM6Ly9kZjM0cmE0YS5leGVjdXRlLWFwaS51cy13ZXN0LTIuYW1hem9uYXdzLmNvbS9kZWZhdWx0L2Nsb3VkaW5hcnlGbg=='; + + private $range_test_pairs = [ + // integer values + ["200", "200"], [200, "200"], [0, "0"], + // float values + ["200.0", "200.0"], [200.0, "200.0"], [200.123, "200.123"], [200.123000, "200.123"], [0.0, "0.0"], + //percent values + ["20p", "20p"], ["20P", "20p"], ["20%", "20p"], ["20.5%", "20.5p"], + // invalid values + ["p", null], ["", null], [null, null], ["non_auto", null], + ]; + + private $original_user_platform; + + public function setUp() + { + Cloudinary::reset_config(); + Cloudinary::config( + array( + "cloud_name" => "test123", + "api_key" => "a", + "api_secret" => "b", + "secure_distribution" => null, + "private_cdn" => false, + "cname" => null + ) + ); + + $this->original_user_platform = \Cloudinary::$USER_PLATFORM; + } + + public function tearDown() + { + parent::TearDown(); + \Cloudinary::$USER_PLATFORM = $this->original_user_platform; + } + + public function test_cloud_name() + { + // should use cloud_name from config + $result = Cloudinary::cloudinary_url("test"); + $this->assertEquals(CloudinaryTest::DEFAULT_UPLOAD_PATH . "test", $result); + } + + public function test_cloud_name_options() + { + // should allow overriding cloud_name in $options + $options = array("cloud_name" => "test321"); + $this->cloudinary_url_assertion("test", $options, "http://res.cloudinary.com/test321/image/upload/test"); + } + + public function test_user_agent() + { + $user_agent = \Cloudinary::userAgent(); + + $this->assertRegExp("/^CloudinaryPHP\/\d+\.\d+\.\d+ \(PHP \d+\.\d+\.\d+\)$/", $user_agent); + + $platform_information = 'TestPlatformInformation (From \"CloudinaryTest.php\")'; + \Cloudinary::$USER_PLATFORM = $platform_information; + $full_user_agent = \Cloudinary::userAgent(); + + $this->assertEquals( + $platform_information . ' ' . $user_agent, + $full_user_agent, + "USER_AGENT should include platform information if set" + ); + } + + public function test_secure_distribution() + { + // should use default secure distribution if secure=TRUE + $options = array("secure" => true); + $this->cloudinary_url_assertion("test", $options, "https://res.cloudinary.com/test123/image/upload/test"); + } + + public function test_secure_distribution_from_config() + { + // should use default secure distribution if secure=true set in config + Cloudinary::config(array("secure" => true)); + $this->cloudinary_url_assertion("test", array(), "https://res.cloudinary.com/test123/image/upload/test"); + } + + public function test_secure_distribution_overwrite() + { + // should allow overwriting secure distribution if secure=TRUE + $options = array("secure" => true, "secure_distribution" => "something.else.com"); + $this->cloudinary_url_assertion("test", $options, "https://something.else.com/test123/image/upload/test"); + } + + public function test_secure_distibution() + { + // should take secure distribution from config if secure=TRUE + Cloudinary::config(array("secure_distribution" => "config.secure.distribution.com")); + $options = array("secure" => true); + $this->cloudinary_url_assertion( + "test", + $options, + "https://config.secure.distribution.com/test123/image/upload/test" + ); + } + + public function test_secure_akamai() + { + // should default to akamai if secure is given with private_cdn and no secure_distribution + $options = array("secure" => true, "private_cdn" => true); + $this->cloudinary_url_assertion("test", $options, "https://test123-res.cloudinary.com/image/upload/test"); + } + + public function test_secure_non_akamai() + { + // should not add cloud_name if private_cdn and secure non akamai secure_distribution + $options = array("secure" => true, "private_cdn" => true, "secure_distribution" => "something.cloudfront.net"); + $this->cloudinary_url_assertion("test", $options, "https://something.cloudfront.net/image/upload/test"); + } + + public function test_http_private_cdn() + { + // should not add cloud_name if private_cdn and not secure + $options = array("private_cdn" => true); + $this->cloudinary_url_assertion("test", $options, "http://test123-res.cloudinary.com/image/upload/test"); + } + + public function test_secure_shared_subdomain() + { + // should support cdn_subdomain with secure on if using shared_domain + $options = array("cdn_subdomain" => true, "secure" => true); + $this->cloudinary_url_assertion("test", $options, "https://res-2.cloudinary.com/test123/image/upload/test"); + } + + public function test_secure_shared_subdomain_false() + { + // should support secure_cdn_subdomain false override with secure + $options = array("cdn_subdomain" => true, "secure" => true, "secure_cdn_subdomain" => false); + $this->cloudinary_url_assertion("test", $options, "https://res.cloudinary.com/test123/image/upload/test"); + } + + public function test_secure_subdomain_true() + { + // should support secure_cdn_subdomain true override with secure + $options = array( + "cdn_subdomain" => true, + "secure" => true, + "secure_cdn_subdomain" => true, + "private_cdn" => true, + ); + $this->cloudinary_url_assertion("test", $options, "https://test123-res-2.cloudinary.com/image/upload/test"); + } + + public function test_format() + { + // should use format from $options + $options = array("format" => "jpg"); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "test.jpg"); + } + + public function test_crop() + { + // should use width and height from $options even if crop is not given + $options = array("width" => 100, "height" => 100); + $result = Cloudinary::cloudinary_url("test", $options); + $this->assertEquals(CloudinaryTest::DEFAULT_UPLOAD_PATH . "h_100,w_100/test", $result); + $this->assertEquals(array("width" => 100, "height" => 100), $options); + $options = array("width" => 100, "height" => 100, "crop" => "crop"); + $result = Cloudinary::cloudinary_url("test", $options); + $this->assertEquals(array("width" => 100, "height" => 100), $options); + $this->assertEquals(CloudinaryTest::DEFAULT_UPLOAD_PATH . "c_crop,h_100,w_100/test", $result); + } + + public function test_various_options() + { + // should use x, y, radius, prefix, gravity and quality from $options + $options = array( + "x" => 1, + "y" => 2, + "radius" => 3, + "gravity" => "center", + "quality" => 0.4, + "prefix" => "a", + "opacity" => 20, + ); + $this->cloudinary_url_assertion( + "test", + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "g_center,o_20,p_a,q_0.4,r_3,x_1,y_2/test" + ); + $options = array("gravity" => "auto", "crop" => "crop", "width" => 0.5); + $this->cloudinary_url_assertion( + "test", + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "c_crop,g_auto,w_0.5/test" + ); + $options = array("gravity" => "auto:ocr_text", "crop" => "crop", "width" => 0.5); + $this->cloudinary_url_assertion( + "test", + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "c_crop,g_auto:ocr_text,w_0.5/test" + ); + $options = array("gravity" => "ocr_text", "crop" => "crop", "width" => 0.5); + $this->cloudinary_url_assertion( + "test", + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "c_crop,g_ocr_text,w_0.5/test" + ); + } + + public function test_quality() + { + $this->cloudinary_url_assertion( + "test", + array("x" => 1, "y" => 2, "radius" => 3, "gravity" => "center", "quality" => 80, "prefix" => "a"), + CloudinaryTest::DEFAULT_UPLOAD_PATH . "g_center,p_a,q_80,r_3,x_1,y_2/test" + ); + $this->cloudinary_url_assertion( + "test", + array("x" => 1, "y" => 2, "radius" => 3, "gravity" => "center", "quality" => "80:444", "prefix" => "a"), + CloudinaryTest::DEFAULT_UPLOAD_PATH . "g_center,p_a,q_80:444,r_3,x_1,y_2/test" + ); + $this->cloudinary_url_assertion( + "test", + array("x" => 1, "y" => 2, "radius" => 3, "gravity" => "center", "quality" => "auto", "prefix" => "a"), + CloudinaryTest::DEFAULT_UPLOAD_PATH . "g_center,p_a,q_auto,r_3,x_1,y_2/test" + ); + $this->cloudinary_url_assertion( + "test", + array("x" => 1, "y" => 2, "radius" => 3, "gravity" => "center", "quality" => "auto:good", "prefix" => "a"), + CloudinaryTest::DEFAULT_UPLOAD_PATH . "g_center,p_a,q_auto:good,r_3,x_1,y_2/test" + ); + } + + /** + * should support a string, integer and array of mixed types + */ + + public function test_radius() + { + $radius_test_values = [ + [10, "r_10"], + ['10', 'r_10'], + ['$v', 'r_$v'], + [[10, 20, 30], 'r_10:20:30'], + [[10, 20, '$v'], 'r_10:20:$v'], + [[10, 20, '$v', 40], 'r_10:20:$v:40'], + [['10:20'], 'r_10:20'], + [['10:20:$v:40'], 'r_10:20:$v:40'] + ]; + + foreach ($radius_test_values as $value) { + $this->cloudinary_url_assertion( + CloudinaryTest::TEST_ID, + array("radius" => $value[0]), + CloudinaryTest::DEFAULT_UPLOAD_PATH . $value[1] . '/' . CloudinaryTest::TEST_ID + ); + } + } + + + public function test_no_empty_options() + { + // should use x, y, width, height, crop, prefix and opacity from $options + $options = array( + "x" => 0, + "y" => '0', + "width" => '', + "height" => "", + "crop" => ' ', + "prefix" => false, + "opacity" => null, + ); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "x_0,y_0/test"); + } + + public function test_transformation_simple() + { + // should support named transformation + $options = array("transformation" => "blip"); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "t_blip/test"); + } + + public function test_transformation_array() + { + // should support array of named transformations + $options = array("transformation" => array("blip", "blop")); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "t_blip.blop/test"); + } + + public function test_base_transformations() + { + // should support base transformation + $options = array( + "transformation" => array("x" => 100, "y" => 100, "crop" => "fill"), + "crop" => "crop", + "width" => 100, + ); + $result = Cloudinary::cloudinary_url("test", $options); + $this->assertEquals(array("width" => 100), $options); + $this->assertEquals(CloudinaryTest::DEFAULT_UPLOAD_PATH . "c_fill,x_100,y_100/c_crop,w_100/test", $result); + } + + public function test_base_transformation_array() + { + // should support array of base transformations + $options = array( + "transformation" => array( + array("x" => 100, "y" => 100, "width" => 200, "crop" => "fill"), + array("radius" => 10), + ), + "crop" => "crop", + "width" => 100, + ); + $result = Cloudinary::cloudinary_url("test", $options); + $this->assertEquals(array("width" => 100), $options); + $this->assertEquals( + CloudinaryTest::DEFAULT_UPLOAD_PATH . "c_fill,w_200,x_100,y_100/r_10/c_crop,w_100/test", + $result + ); + } + + public function test_no_empty_transformation() + { + // should not include empty transformations + $options = array("transformation" => array(array(), array("x" => 100, "y" => 100, "crop" => "fill"), array())); + $this->cloudinary_url_assertion( + "test", + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "c_fill,x_100,y_100/test" + ); + } + + /** + * Should support chaining transformations at the end + */ + public function test_chain_transformations() + { + $options = ["effect" => "art:incognito", "format" => "png"]; + + $chained_transformations = [ + ["x" => 100, "y" => 100, "width" => 200, "crop" => "fill"], + ["radius" => 10], + ["raw_transformation" => self::$raw_transformation] + ]; + + $actual_options = Cloudinary::chain_transformations($options, $chained_transformations); + $actual_transformation_str = Cloudinary::generate_transformation_string($actual_options); + + $this->assertEquals( + "e_art:incognito/c_fill,w_200,x_100,y_100/r_10/" . self::$raw_transformation, + $actual_transformation_str, + "it should chain an array of transformations" + ); + + $message = "Should support chaining transformations, when default options have no transformations"; + $actual_options = Cloudinary::chain_transformations([], $chained_transformations); + $actual_transformation_str = Cloudinary::generate_transformation_string($actual_options); + + $this->assertEquals( + "c_fill,w_200,x_100,y_100/r_10/" . self::$raw_transformation, + $actual_transformation_str, + $message + ); + + $message = "Should handle empty list of chained transformations"; + $actual_options = Cloudinary::chain_transformations($options, []); + $actual_transformation_str = Cloudinary::generate_transformation_string($actual_options); + + $this->assertEquals("e_art:incognito", $actual_transformation_str, $message); + + $message = "Should handle empty options and empty list of chained transformations"; + $actual_options = Cloudinary::chain_transformations([], []); + $actual_transformation_str = Cloudinary::generate_transformation_string($actual_options); + + $this->assertEquals("", $actual_transformation_str, $message); + + $message = "Should remove transformation options from resulting options"; + $actual_options = Cloudinary::chain_transformations( + ["width" => 200, "height" => 100], + $chained_transformations + ); + + $this->assertArrayNotHasKey("width", $actual_options, $message); + $this->assertArrayNotHasKey("height", $actual_options, $message); + + $actual_transformation_str = Cloudinary::generate_transformation_string($actual_options); + + $this->assertEquals( + "h_100,w_200/c_fill,w_200,x_100,y_100/r_10/c_fill,e_grayscale,q_auto", + $actual_transformation_str, + $message + ); + + $message = "Should chain transformations with a fetch option"; + $options["type"] = "fetch"; + + Cloudinary::patch_fetch_format($options); + $actual_options = Cloudinary::chain_transformations($options, $chained_transformations); + + // format should be removed when we use fetch + $this->assertArrayNotHasKey("format", $actual_options, $message); + + $actual_transformation_str = Cloudinary::generate_transformation_string($actual_options); + + $message = "should use url format as a fetch_format"; + $this->assertEquals( + "e_art:incognito,f_png/c_fill,w_200,x_100,y_100/r_10/" . self::$raw_transformation, + $actual_transformation_str, + $message + ); + + $message = "should use fetch_format"; + $options["fetch_format"] = "gif"; + + $actual_options = Cloudinary::chain_transformations($options, $chained_transformations); + $actual_transformation_str = Cloudinary::generate_transformation_string($actual_options); + + // should use fetch_format + $this->assertEquals( + "e_art:incognito,f_gif/c_fill,w_200,x_100,y_100/r_10/" . self::$raw_transformation, + $actual_transformation_str, + $message + ); + } + + public function test_size() + { + // should support size + $options = array("size" => "10x10", "crop" => "crop"); + $result = Cloudinary::cloudinary_url("test", $options); + $this->assertEquals(array("width" => "10", "height" => "10"), $options); + $this->assertEquals(CloudinaryTest::DEFAULT_UPLOAD_PATH . "c_crop,h_10,w_10/test", $result); + } + + public function test_type() + { + // should use type from $options + $options = array("type" => "facebook"); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_ROOT_PATH . "image/facebook/test"); + } + + public function test_resource_type() + { + // should use resource_type from $options + $options = array("resource_type" => "raw"); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_ROOT_PATH . "raw/upload/test"); + } + + public function test_ignore_http() + { + // should ignore http links only if type is not given + $options = array(); + $this->cloudinary_url_assertion("http://test", $options, "http://test"); + $options = array("type" => "fetch"); + $this->cloudinary_url_assertion( + "http://test", + $options, + CloudinaryTest::DEFAULT_ROOT_PATH . "image/fetch/http://test" + ); + } + + public function test_fetch() + { + // should escape fetch urls + $options = array("type" => "fetch"); + $this->cloudinary_url_assertion( + "http://blah.com/hello?a=b", + $options, + CloudinaryTest::DEFAULT_ROOT_PATH . "image/fetch/http://blah.com/hello%3Fa%3Db" + ); + } + + public function test_cname() + { + // should support extenal cname + $options = array("cname" => "hello.com"); + $this->cloudinary_url_assertion("test", $options, "http://hello.com/test123/image/upload/test"); + } + + public function test_cname_subdomain() + { + // should support extenal cname with cdn_subdomain on + $options = array("cname" => "hello.com", "cdn_subdomain" => true); + $this->cloudinary_url_assertion("test", $options, "http://a2.hello.com/test123/image/upload/test"); + } + + public function test_http_escape() + { + // should escape http urls + $options = array("type" => "youtube"); + $this->cloudinary_url_assertion( + "http://www.youtube.com/watch?v=d9NF2edxy-M", + $options, + CloudinaryTest::DEFAULT_ROOT_PATH . "image/youtube/http://www.youtube.com/watch%3Fv%3Dd9NF2edxy-M" + ); + } + + public function test_background() + { + // should support background + $options = array("background" => "red"); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "b_red/test"); + $options = array("background" => "#112233"); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "b_rgb:112233/test"); + } + + public function test_default_image() + { + // should support default_image + $options = array("default_image" => "default"); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "d_default/test"); + } + + public function test_angle() + { + // should support angle + $options = array("angle" => 12); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "a_12/test"); + $options = array("angle" => array("auto", 12)); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "a_auto.12/test"); + } + + public function test_overlay() + { + // should support overlay + $options = array("overlay" => "text:hello"); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "l_text:hello/test"); + // should not pass width/height to html if overlay + $options = array("overlay" => "text:hello", "width" => 100, "height" => 100); + $this->cloudinary_url_assertion( + "test", + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "h_100,l_text:hello,w_100/test" + ); + } + + public function test_underlay() + { + // should support underlay + $options = array("underlay" => "text:hello"); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "u_text:hello/test"); + // should not pass width/height to html if underlay + $options = array("underlay" => "text:hello", "width" => 100, "height" => 100); + $this->cloudinary_url_assertion( + "test", + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "h_100,u_text:hello,w_100/test" + ); + } + + public function test_overlay_fetch() + { + // should support overlay from a fetch url + $options = array("overlay" => "fetch:http://cloudinary.com/images/old_logo.png"); + $this->cloudinary_url_assertion( + "test", + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "l_fetch:aHR0cDovL2Nsb3VkaW5hcnkuY29tL2ltYWdlcy9vbGRfbG9nby5wbmc=/test" + ); + } + + public function test_underlay_fetch() + { + // should support underlay from a fetch url + $options = array("underlay" => "fetch:http://cloudinary.com/images/old_logo.png"); + $this->cloudinary_url_assertion( + "test", + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "u_fetch:aHR0cDovL2Nsb3VkaW5hcnkuY29tL2ltYWdlcy9vbGRfbG9nby5wbmc=/test" + ); + } + + public function test_fetch_format() + { + // should support format for fetch urls + $options = array("format" => "jpg", "type" => "fetch"); + $this->cloudinary_url_assertion( + "http://cloudinary.com/images/logo.png", + $options, + CloudinaryTest::DEFAULT_ROOT_PATH . "image/fetch/f_jpg/http://cloudinary.com/images/logo.png" + ); + } + + public function test_streaming_profile() + { + // should support streaming profile + $options = array("streaming_profile" => "some-profile"); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "sp_some-profile/test"); + } + + public function test_effect() + { + // should support effect + $options = array("effect" => "sepia"); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "e_sepia/test"); + } + + public function test_effect_with_array() + { + // should support effect with array + $options = array("effect" => array("sepia", -10)); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "e_sepia:-10/test"); + } + + public function test_density() + { + // should support density + $options = array("density" => 150); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "dn_150/test"); + } + + public function test_custom_function() + { + $test_id = self::TEST_ID; + $wasm_str = self::$custom_function_wasm_str; + + // should support custom function from string + $options = array('custom_function' => self::$custom_function_wasm_str); + $this->cloudinary_url_assertion( + $test_id, + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "fn_$wasm_str/$test_id" + ); + + // should support custom function from array + $options = array('custom_function' => self::$custom_function_wasm); + $this->cloudinary_url_assertion( + $test_id, + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "fn_$wasm_str/$test_id" + ); + + $remote_str = self::$custom_function_remote_str; + // should encode custom function source for remote function + $options = array('custom_function' => self::$custom_function_remote); + $this->cloudinary_url_assertion( + $test_id, + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "fn_$remote_str/$test_id" + ); + } + + public function test_custom_pre_function_string() + { + $test_id = self::TEST_ID; + $wasm_str = self::$custom_function_wasm_str; + + // should support custom pre function from string + $options = array('custom_pre_function' => self::$custom_function_wasm_str); + $this->cloudinary_url_assertion( + $test_id, + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "fn_pre:$wasm_str/$test_id" + ); + } + + public function test_custom_pre_function_wasm_array() + { + $test_id = self::TEST_ID; + $wasm_str = self::$custom_function_wasm_str; + + // should support custom pre function from array + $options = array('custom_pre_function' => self::$custom_function_wasm); + $this->cloudinary_url_assertion( + $test_id, + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "fn_pre:$wasm_str/$test_id" + ); + } + + public function test_custom_pre_function_remote() + { + $test_id = self::TEST_ID; + $remote_str = self::$custom_function_remote_str; + + // should encode custom pre function source for remote pre function + $options = array('custom_pre_function' => self::$custom_function_remote); + $this->cloudinary_url_assertion( + $test_id, + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "fn_pre:$remote_str/$test_id" + ); + } + + public function test_page() + { + // should support page + $options = array("page" => 5); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "pg_5/test"); + } + + public function test_border() + { + // should support border + $options = array("border" => array("width" => 5)); + $this->cloudinary_url_assertion( + "test", + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "bo_5px_solid_black/test" + ); + $options = array("border" => array("width" => 5, "color" => "#ffaabbdd")); + $this->cloudinary_url_assertion( + "test", + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "bo_5px_solid_rgb:ffaabbdd/test" + ); + $options = array("border" => "1px_solid_blue"); + $this->cloudinary_url_assertion( + "test", + $options, + CloudinaryTest::DEFAULT_UPLOAD_PATH . "bo_1px_solid_blue/test" + ); + } + + public function test_flags() + { + // should support flags + $options = array("flags" => "abc"); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "fl_abc/test"); + $options = array("flags" => array("abc", "def")); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "fl_abc.def/test"); + } + + public function test_aspect_ratio() + { + // should support background + $options = array("aspect_ratio" => "1.0"); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "ar_1.0/test"); + $options = array("aspect_ratio" => "3:2"); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_UPLOAD_PATH . "ar_3:2/test"); + } + + public function test_e_art_incognito() + { + $options = array("effect" => "art:incognito", "format" => "png"); + $tag = Cloudinary::generate_transformation_string($options); + $this->assertEquals( + "e_art:incognito", + $tag + ); + } + + public function test_folder_version() + { + // should add version if public_id contains / + $this->cloudinary_url_assertion( + self::TEST_FOLDER, + array(), + CloudinaryTest::DEFAULT_UPLOAD_PATH . self::DEFAULT_VERSION_STR . '/' . self::TEST_FOLDER + ); + $this->cloudinary_url_assertion( + self::TEST_FOLDER, + array('version' => self::IMAGE_VERSION), + CloudinaryTest::DEFAULT_UPLOAD_PATH . self::IMAGE_VERSION_STR . '/' . self::TEST_FOLDER + ); + $this->cloudinary_url_assertion( + self::IMAGE_VERSION_STR . '/' . self::TEST_ID, + array(), + CloudinaryTest::DEFAULT_UPLOAD_PATH . self::IMAGE_VERSION_STR . '/' . self::TEST_ID + ); + } + + /** + * Should not set default version v1 to resources stored in folders if force_version is set to false + */ + public function test_force_version() + { + $this->cloudinary_url_assertion( + self::TEST_FOLDER, + array(), + CloudinaryTest::DEFAULT_UPLOAD_PATH . self::DEFAULT_VERSION_STR . '/' . self::TEST_FOLDER + ); + + $this->cloudinary_url_assertion( + self::TEST_FOLDER, + array('force_version' => false), + CloudinaryTest::DEFAULT_UPLOAD_PATH . self::TEST_FOLDER + ); + + // Explicitly set version is always passed + $this->cloudinary_url_assertion( + self::TEST_ID, + array( + 'version' => self::IMAGE_VERSION, + 'force_version' => false + ), + CloudinaryTest::DEFAULT_UPLOAD_PATH . self::IMAGE_VERSION_STR . '/' . self::TEST_ID + ); + + $this->cloudinary_url_assertion( + self::TEST_FOLDER, + array( + 'version' => self::IMAGE_VERSION, + 'force_version' => false + ), + CloudinaryTest::DEFAULT_UPLOAD_PATH . self::IMAGE_VERSION_STR . '/' . self::TEST_FOLDER + ); + + // Should use force_version from config + Cloudinary::config(array('force_version' => false)); + + $this->cloudinary_url_assertion( + self::TEST_FOLDER, + array(), + CloudinaryTest::DEFAULT_UPLOAD_PATH . self::TEST_FOLDER + ); + + // Should override config with options + $this->cloudinary_url_assertion( + self::TEST_FOLDER, + array('force_version' => true), + CloudinaryTest::DEFAULT_UPLOAD_PATH . self::DEFAULT_VERSION_STR . '/' . self::TEST_FOLDER + ); + } + + public function test_shorten() + { + $options = array("shorten" => true); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_ROOT_PATH . "iu/test"); + + $options = array("shorten" => true, "type" => "private"); + $this->cloudinary_url_assertion("test", $options, CloudinaryTest::DEFAULT_ROOT_PATH . "image/private/test"); + } + + public function test_signed_url() + { + // should correctly sign a url + $this->cloudinary_url_assertion( + "image.jpg", + array( + "version" => 1234, + "transformation" => array("crop" => "crop", "width" => 10, "height" => 20), + "sign_url" => true, + ), + CloudinaryTest::DEFAULT_UPLOAD_PATH . "s--Ai4Znfl3--/c_crop,h_20,w_10/v1234/image.jpg" + ); + $this->cloudinary_url_assertion( + "image.jpg", + array("version" => 1234, "sign_url" => true), + CloudinaryTest::DEFAULT_UPLOAD_PATH . "s----SjmNDA--/v1234/image.jpg" + ); + $this->cloudinary_url_assertion( + "image.jpg", + array("transformation" => array("crop" => "crop", "width" => 10, "height" => 20), "sign_url" => true), + CloudinaryTest::DEFAULT_UPLOAD_PATH . "s--Ai4Znfl3--/c_crop,h_20,w_10/image.jpg" + ); + $this->cloudinary_url_assertion( + "image.jpg", + array( + "transformation" => array("crop" => "crop", "width" => 10, "height" => 20), + "type" => "authenticated", + "sign_url" => true, + ), + CloudinaryTest::DEFAULT_ROOT_PATH . "image/authenticated/s--Ai4Znfl3--/c_crop,h_20,w_10/image.jpg" + ); + $this->cloudinary_url_assertion( + "http://google.com/path/to/image.png", + array("type" => "fetch", "version" => 1234, "sign_url" => true), + CloudinaryTest::DEFAULT_ROOT_PATH . "image/fetch/s--hH_YcbiS--/v1234/http://google.com/path/to/image.png" + ); + } + + public function test_escape_public_id() + { + //should escape public_ids + $tests = array( + "a b" => "a%20b", + "a+b" => "a%2Bb", + "a%20b" => "a%20b", + "a-b" => "a-b", + "a??b" => "a%3F%3Fb", + "parentheses(interject)" => "parentheses%28interject%29", + ); + foreach ($tests as $source => $target) { + $url = Cloudinary::cloudinary_url($source); + $this->assertEquals(CloudinaryTest::DEFAULT_UPLOAD_PATH . "$target", $url); + } + } + + /** + * Should support url_suffix in shared distribution + */ + public function test_allow_url_suffix_in_shared() + { + $options = array("url_suffix" => "hello"); + $url = Cloudinary::cloudinary_url("test", $options); + $this->assertEquals(CloudinaryTest::DEFAULT_ROOT_PATH . "images/test/hello", $url); + } + + /** + * @expectedException InvalidArgumentException + */ + public function test_disallow_url_suffix_with_non_upload_types() + { + //should disallow url_suffix in non upload types + $options = array("url_suffix" => "hello", "private_cdn" => true, "type" => "facebook"); + Cloudinary::cloudinary_url("test", $options); + } + + /** + * @expectedException InvalidArgumentException + */ + public function test_disallow_suffix_with_dot() + { + //should disallow url_suffix with . + $options = array("url_suffix" => "hello/world", "private_cdn" => true); + Cloudinary::cloudinary_url("test", $options); + } + + /** + * @expectedException InvalidArgumentException + */ + public function test_disallow_suffix_with_slash() + { + //should disallow url_suffix with / + $options = array("url_suffix" => "hello/world", "private_cdn" => true); + Cloudinary::cloudinary_url("test", $options); + } + + + public function test_url_suffix_for_private_cdn() + { + //should support url_suffix for private_cdn + $this->cloudinary_url_assertion( + "test", + array("url_suffix" => "hello", "private_cdn" => true), + "http://test123-res.cloudinary.com/images/test/hello" + ); + $this->cloudinary_url_assertion( + "test", + array("url_suffix" => "hello", "transformation" => array("angle" => 0), "private_cdn" => true), + "http://test123-res.cloudinary.com/images/a_0/test/hello" + ); + } + + public function test_format_after_url_suffix() + { + //should put format after url_suffix + $this->cloudinary_url_assertion( + "test", + array("url_suffix" => "hello", "private_cdn" => true, "format" => "jpg"), + "http://test123-res.cloudinary.com/images/test/hello.jpg" + ); + } + + public function test_dont_sign_the_url_suffix() + { + //should not sign the url_suffix + $options = array("format" => "jpg", "sign_url" => true); + preg_match('/s--[0-9A-Za-z_-]{8}--/', Cloudinary::cloudinary_url("test", $options), $matches); + $this->cloudinary_url_assertion( + "test", + array("url_suffix" => "hello", "private_cdn" => true, "format" => "jpg", "sign_url" => true), + "http://test123-res.cloudinary.com/images/" . $matches[0] . "/test/hello.jpg" + ); + + $options = array("format" => "jpg", "angle" => 0, "sign_url" => true); + preg_match('/s--[0-9A-Za-z_-]{8}--/', Cloudinary::cloudinary_url("test", $options), $matches); + $this->cloudinary_url_assertion( + "test", + array( + "url_suffix" => "hello", + "private_cdn" => true, + "format" => "jpg", + "transformation" => array("angle" => 0), + "sign_url" => true, + ), + "http://test123-res.cloudinary.com/images/" . $matches[0] . "/a_0/test/hello.jpg" + ); + } + + public function test_url_suffix_for_raw() + { + //should support url_suffix for raw uploads + $this->cloudinary_url_assertion( + "test", + array("url_suffix" => "hello", "private_cdn" => true, "resource_type" => "raw"), + "http://test123-res.cloudinary.com/files/test/hello" + ); + } + + /** + * Should support url_suffix for video uploads + */ + public function test_url_suffix_for_videos() + { + $this->cloudinary_url_assertion( + "test", + array("url_suffix" => "hello", "private_cdn" => true, "resource_type" => "video"), + "http://test123-res.cloudinary.com/videos/test/hello" + ); + } + + /** + * Should support url_suffix for private images + */ + public function test_url_suffix_for_private() + { + $this->cloudinary_url_assertion( + "test", + array("url_suffix" => "hello", "private_cdn" => true, "resource_type" => "image", "type" => "private"), + "http://test123-res.cloudinary.com/private_images/test/hello" + ); + + $this->cloudinary_url_assertion( + "test", + array( + "url_suffix" => "hello", + "private_cdn" => true, + "format" => "jpg", + "resource_type" => "image", + "type" => "private", + ), + "http://test123-res.cloudinary.com/private_images/test/hello.jpg" + ); + } + + /** + * Should support url_suffix for authenticated images + */ + public function test_url_suffix_for_authenticated() + { + $this->cloudinary_url_assertion( + "test", + array( + "url_suffix" => "hello", + "private_cdn" => true, + "resource_type" => "image", + "type" => "authenticated" + ), + "http://test123-res.cloudinary.com/authenticated_images/test/hello" + ); + } + + public function test_allow_use_root_path_in_shared() + { + + $this->cloudinary_url_assertion( + "test", + array("use_root_path" => true, "private_cdn" => false), + CloudinaryTest::DEFAULT_ROOT_PATH . "test" + ); + $this->cloudinary_url_assertion( + "test", + array("use_root_path" => true, "private_cdn" => false, "transformation" => array("angle" => 0)), + CloudinaryTest::DEFAULT_ROOT_PATH . "a_0/test" + ); + } + + public function test_use_root_path_for_private_cdn() + { + //should support use_root_path for private_cdn + $this->cloudinary_url_assertion( + "test", + array("use_root_path" => true, "private_cdn" => true), + "http://test123-res.cloudinary.com/test" + ); + $this->cloudinary_url_assertion( + "test", + array("use_root_path" => true, "private_cdn" => true, "transformation" => array("angle" => 0)), + "http://test123-res.cloudinary.com/a_0/test" + ); + } + + public function test_use_root_path_with_url_suffix_for_private_cdn() + { + //should support use_root_path together with url_suffix for private_cdn + $this->cloudinary_url_assertion( + "test", + array("use_root_path" => true, "url_suffix" => "hello", "private_cdn" => true), + "http://test123-res.cloudinary.com/test/hello" + ); + } + + /** + * @expectedException InvalidArgumentException + */ + public function test_disallow_use_root_path_if_not_image_upload_1() + { + //should disallow use_root_path if not image/upload + $options = array("use_root_path" => true, "private_cdn" => true, "type" => "facebook"); + Cloudinary::cloudinary_url("test", $options); + } + + /** + * @expectedException InvalidArgumentException + */ + public function test_disallow_use_root_path_if_not_image_upload_2() + { + //should disallow use_root_path if not image/upload + $options = array("use_root_path" => true, "private_cdn" => true, "resource_type" => "raw"); + Cloudinary::cloudinary_url("test", $options); + } + + public function test_norm_range_value() + { + $method = new ReflectionMethod('Cloudinary', 'norm_range_value'); + $method->setAccessible(true); + foreach ($this->range_test_pairs as $pair) { + $this->assertEquals($method->invoke(null, $pair[0]), $pair[1]); + } + $this->assertNull($method->invoke(null, "auto"), "Shouldn't support 'auto' value"); + } + + public function test_norm_auto_range_value() + { + $method = new ReflectionMethod('Cloudinary', 'norm_auto_range_value'); + $method->setAccessible(true); + foreach ($this->range_test_pairs as $pair) { + $this->assertEquals($method->invoke(null, $pair[0]), $pair[1]); + } + $this->assertEquals($method->invoke(null, "auto"), "auto", "Should support 'auto' value"); + } + + public function test_video_codec() + { + // should support a string value + $this->cloudinary_url_assertion("video_id", array('resource_type' => 'video', 'video_codec' => 'auto'), + CloudinaryTest::VIDEO_UPLOAD_PATH . "vc_auto/video_id"); + // should support a hash value + $this->cloudinary_url_assertion( + "video_id", + array( + 'resource_type' => 'video', + 'video_codec' => array('codec' => 'h264', 'profile' => 'basic', 'level' => '3.1'), + ), + CloudinaryTest::VIDEO_UPLOAD_PATH . "vc_h264:basic:3.1/video_id"); + } + + /** + * Should support a single number, an array of mixed type and a string, including open-ended and closed range values + */ + public function test_fps() + { + $fps_test_values = [ + ['24-29.97', 'fps_24-29.97'], + [24, 'fps_24'], + [24.973, 'fps_24.973'], + ['24', 'fps_24'], + ['-24', 'fps_-24'], + ['$v', 'fps_$v'], + [[24, 29.97], 'fps_24-29.97'], + [['24', '$v'], 'fps_24-$v'] + ]; + + foreach ($fps_test_values as $value) { + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'fps' => $value[0]), + CloudinaryTest::VIDEO_UPLOAD_PATH . $value[1] . "/video_id" + ); + } + } + + /** + * Should support a positive number or a string + */ + public function test_keyframe_interval() + { + $test_values = [ + [10, 'ki_10.0'], + [0.05, 'ki_0.05'], + [3.45, 'ki_3.45'], + [300, 'ki_300.0'], + ['10', 'ki_10'], + ]; + + foreach ($test_values as $value) { + $this->cloudinary_url_assertion( + 'video_id', + array('resource_type' => 'video', 'keyframe_interval' => $value[0]), + CloudinaryTest::VIDEO_UPLOAD_PATH . $value[1] . "/video_id" + ); + } + } + + /** + * @expectedException InvalidArgumentException + */ + public function test_keyframe_interval_positive() { + $options = array('resource_type' => 'video', 'keyframe_interval' => -1); + Cloudinary::cloudinary_url('video_id', $options); + } + + public function test_audio_codec() + { + // should support a string value + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'audio_codec' => 'acc'), + CloudinaryTest::VIDEO_UPLOAD_PATH . "ac_acc/video_id" + ); + } + + public function test_bit_rate() + { + // should support an integer value + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'bit_rate' => 2048), + CloudinaryTest::VIDEO_UPLOAD_PATH . "br_2048/video_id" + ); + // should support "k" + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'bit_rate' => '44k'), + CloudinaryTest::VIDEO_UPLOAD_PATH . "br_44k/video_id" + ); + // should support "m" + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'bit_rate' => '1m'), + CloudinaryTest::VIDEO_UPLOAD_PATH . "br_1m/video_id" + ); + } + + public function test_audio_frequency() + { + // should support an integer value + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'audio_frequency' => 44100), + CloudinaryTest::VIDEO_UPLOAD_PATH . "af_44100/video_id" + ); + } + + public function test_video_sampling() + { + // should support an integer value + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'video_sampling' => 20), + CloudinaryTest::VIDEO_UPLOAD_PATH . "vs_20/video_id" + ); + // should support an string value in the a form of 's' + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'video_sampling' => "2.3s"), + CloudinaryTest::VIDEO_UPLOAD_PATH . "vs_2.3s/video_id" + ); + } + + public function test_start_offset() + { + // should support decimal seconds + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'start_offset' => 2.63), + CloudinaryTest::VIDEO_UPLOAD_PATH . "so_2.63/video_id" + ); + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'start_offset' => '2.63'), + CloudinaryTest::VIDEO_UPLOAD_PATH . "so_2.63/video_id" + ); + // should support percents of the video length as "p" + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'start_offset' => '35p'), + CloudinaryTest::VIDEO_UPLOAD_PATH . "so_35p/video_id" + ); + // should support percents of the video length as "%" + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'start_offset' => '35%'), + CloudinaryTest::VIDEO_UPLOAD_PATH . "so_35p/video_id" + ); + // should support auto select of a suitable frame from the first few seconds of a video + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'start_offset' => 'auto'), + CloudinaryTest::VIDEO_UPLOAD_PATH . "so_auto/video_id" + ); + } + + public function test_end_offset() + { + // should support decimal seconds + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'end_offset' => 2.63), + CloudinaryTest::VIDEO_UPLOAD_PATH . "eo_2.63/video_id" + ); + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'end_offset' => '2.63'), + CloudinaryTest::VIDEO_UPLOAD_PATH . "eo_2.63/video_id" + ); + // should support percents of the video length as "p" + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'end_offset' => '35p'), + CloudinaryTest::VIDEO_UPLOAD_PATH . "eo_35p/video_id" + ); + // should support percents of the video length as "%" + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'end_offset' => '35%'), + CloudinaryTest::VIDEO_UPLOAD_PATH . "eo_35p/video_id" + ); + } + + public function test_duration_parameter() + { + // should support decimal seconds + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'duration' => 2.63), + CloudinaryTest::VIDEO_UPLOAD_PATH . "du_2.63/video_id" + ); + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'duration' => '2.63'), + CloudinaryTest::VIDEO_UPLOAD_PATH . "du_2.63/video_id" + ); + // should support percents of the video length as "p" + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'duration' => '35p'), + CloudinaryTest::VIDEO_UPLOAD_PATH . "du_35p/video_id" + ); + // should support percents of the video length as "%" + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'duration' => '35%'), + CloudinaryTest::VIDEO_UPLOAD_PATH . "du_35p/video_id" + ); + } + + public function test_offset() + { + foreach (array( + 'eo_3.21,so_2.66' => '2.66..3.21', + 'eo_3.22,so_2.67' => array(2.67, 3.22), + 'eo_70p,so_35p' => array('35%', '70%'), + 'eo_71p,so_36p' => array('36p', '71p'), + 'eo_70.5p,so_35.5p' => array('35.5p', '70.5p'), + ) as $transformation => $offset + ) { + $this->cloudinary_url_assertion( + "video_id", + array('resource_type' => 'video', 'offset' => $offset), + CloudinaryTest::VIDEO_UPLOAD_PATH . $transformation . "/video_id" + ); + } + } + + public function layers_options() + { + return array( + "public_id" => array(array("public_id" => "logo"), "logo"), + "public_id with folder" => array(array("public_id" => "folder/logo"), "folder:logo"), + "private" => array(array("public_id" => "logo", "type" => "private"), "private:logo"), + "format" => array(array("public_id" => "logo", "format" => "png"), "logo.png"), + "video" => array(array("resource_type" => "video", "public_id" => "cat"), "video:cat"), + "text" => array( + array("public_id" => "logo", "text" => "Hello World, Nice to meet you?"), + "text:logo:Hello%20World%252C%20Nice%20to%20meet%20you%3F", + ), + "text with slash" => array( + array( + "text" => "Hello World, Nice/ to meet you?", + "font_family" => "Arial", + "font_size" => "18", + ), + "text:Arial_18:Hello%20World%252C%20Nice%252F%20to%20meet%20you%3F", + ), + "text with font family and size" => array( + array( + "text" => "Hello World, Nice to meet you?", + "font_family" => "Arial", + "font_size" => "18", + ), + "text:Arial_18:Hello%20World%252C%20Nice%20to%20meet%20you%3F", + ), + "text with style" => array( + array( + "text" => "Hello World, Nice to meet you?", + "font_family" => "Arial", + "font_size" => "18", + "font_weight" => "bold", + "font_style" => "italic", + "letter_spacing" => 4, + ), + "text:Arial_18_bold_italic_letter_spacing_4:Hello%20World%252C%20Nice%20to%20meet%20you%3F", + ), + "text with antialiasing and hinting" => array( + array( + "text" => "Hello World, Nice to meet you?", + "font_family" => "Arial", + "font_size" => "18", + "font_antialiasing" => "best", + "font_hinting" => "medium" + ), + "text:Arial_18_antialias_best_hinting_medium:Hello%20World%252C%20Nice%20to%20meet%20you%3F", + ), + "subtitles" => array( + array("resource_type" => "subtitles", "public_id" => "sample_sub_en.srt"), + "subtitles:sample_sub_en.srt", + ), + "subtitles with font family and size" => array( + array( + "resource_type" => "subtitles", + "public_id" => "sample_sub_he.srt", + "font_family" => "Arial", + "font_size" => "40", + ), + "subtitles:Arial_40:sample_sub_he.srt", + ), + "fetch" => array( + array("public_id" => "logo", 'fetch' => 'https://cloudinary.com/images/old_logo.png'), + 'fetch:aHR0cHM6Ly9jbG91ZGluYXJ5LmNvbS9pbWFnZXMvb2xkX2xvZ28ucG5n', + ), + + ); + } + + /** + * @dataProvider layers_options + */ + public function test_overlay_options($options, $expected) + { + $reflector = new ReflectionClass('Cloudinary'); + $process_layer = $reflector->getMethod('process_layer'); + $process_layer->setAccessible(true); + $result = $process_layer->invoke(null, $options, "overlay"); + $this->assertEquals($expected, $result); + } + + public function test_ignore_default_values_in_overlay_options() + { + $options = array("public_id" => "logo", "type" => "upload", "resource_type" => "image"); + $expected = "logo"; + $reflector = new ReflectionClass('Cloudinary'); + $process_layer = $reflector->getMethod('process_layer'); + $process_layer->setAccessible(true); + $result = $process_layer->invoke(null, $options, "overlay"); + $this->assertEquals($expected, $result); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Must supply either style parameters or a public_id + * when providing text parameter in a text overlay + */ + public function test_text_require_public_id_or_style() + { + $options = array("overlay" => array("text" => "text")); + Cloudinary::cloudinary_url("test", $options); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Must supply font_family for text in overlay + */ + public function test_overlay_style_requires_font_family() + { + $options = array("overlay" => array("text" => "text", "font_style" => "italic")); + Cloudinary::cloudinary_url("test", $options); + } + + public function resource_types() + { + return array( + "image" => array("image"), + "video" => array("video"), + "raw" => array("raw"), + "subtitles" => array("subtitles"), + ); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessageRegExp #Must supply public_id for .* underlay# + * @dataProvider resource_types + */ + public function test_underlay_require_public_id_for_non_text($resource_type) + { + $options = array("underlay" => array("resource_type" => $resource_type)); + Cloudinary::cloudinary_url("test", $options); + } + + /** + * should support and translate operators: '=', '!=', '<', '>', '<=', '>=', '&&', '||' + * and variables: width, height, pages, faces, aspect_ratio + */ + public function test_translate_if() + { + $allOperators = + 'if_' . + 'w_eq_0_and' . + '_h_ne_0_or' . + '_ar_lt_0_and' . + '_pc_gt_0_and' . + '_fc_lte_0_and' . + '_w_gte_0' . + ',e_grayscale'; + $condition = "width = 0 && height != 0 || aspect_ratio < 0 && page_count > 0 and face_count <= 0 and width >= 0"; + $options = array("if" => $condition, "effect" => "grayscale"); + $transformation = Cloudinary::generate_transformation_string($options); + $this->assertEquals($allOperators, $transformation); + $this->assertEquals(array(), $options); + $options = array("if" => "aspect_ratio > 0.3 && aspect_ratio < 0.5", "effect" => "grayscale"); + $transformation = Cloudinary::generate_transformation_string($options); + $this->assertEquals("if_ar_gt_0.3_and_ar_lt_0.5,e_grayscale", $transformation); + $this->assertEquals(array(), $options); + } + + public function test_normalize_expression_should_not_convert_user_variables() + { + $options = array( + 'transformation' => array( + array('$width' => 10), + array('width' => '$width + 10 + width'), + ), + ); + + $t = Cloudinary::generate_transformation_string($options); + + $this->assertEquals('$width_10/w_$width_add_10_add_w', $t); + } + + public function test_array_should_define_set_of_variables() + { + $options = array( + 'if' => "face_count > 2", + 'crop' => "scale", + 'width' => '$foo * 200', + 'variables' => array( + '$z' => 5, + '$foo' => '$z * 2', + ), + ); + + $t = Cloudinary::generate_transformation_string($options); + $this->assertEquals('if_fc_gt_2,$z_5,$foo_$z_mul_2,c_scale,w_$foo_mul_200', $t); + } + + public function test_duration_variable() + { + $options = array('if' => "duration > 30", 'width' => '100', 'crop' => "scale"); + $t = Cloudinary::generate_transformation_string($options); + + $this->assertEquals('if_du_gt_30,c_scale,w_100', $t); + + $options = array('if' => "initial_duration > 30", 'width' => '100', 'crop' => "scale"); + $t = Cloudinary::generate_transformation_string($options); + + $this->assertEquals('if_idu_gt_30,c_scale,w_100', $t); + } + + public function test_key_should_define_variable() + { + $options = array( + 'transformation' => array( + array('$foo' => 10), + array('if' => "face_count > 2"), + array('crop' => "scale", 'width' => '$foo * 200 / face_count'), + array('if' => "end"), + ), + ); + + $t = Cloudinary::generate_transformation_string($options); + $this->assertEquals('$foo_10/if_fc_gt_2/c_scale,w_$foo_mul_200_div_fc/if_end', $t); + } + + public function test_url_should_convert_operators() + { + $options = array( + 'transformation' => array( + array('width' => 'initial_width ^ 2','height' => 'initial_height * 2', 'crop' => 'scale'), + ), + ); + + $result = Cloudinary::cloudinary_url("test", $options); + + $this->assertEquals(CloudinaryTest::DEFAULT_UPLOAD_PATH . 'c_scale,h_ih_mul_2,w_iw_pow_2/test', $result); + } + + public function test_should_support_streaming_profile() + { + $options = array( + 'streaming_profile' => 'some_profile', + ); + + $t = Cloudinary::generate_transformation_string($options); + $this->assertEquals('sp_some_profile', $t); + } + + public function test_should_sort_defined_variable() + { + $options = array( + '$second' => 1, + '$first' => 2, + ); + + $t = Cloudinary::generate_transformation_string($options); + $this->assertEquals('$first_2,$second_1', $t); + } + + public function test_should_place_defined_variables_before_ordered() + { + $options = array( + 'variables' => array( + '$z' => 5, + '$foo' => '$z * 2', + ), + '$second' => 1, + '$first' => 2, + ); + + $t = Cloudinary::generate_transformation_string($options); + $this->assertEquals('$first_2,$second_1,$z_5,$foo_$z_mul_2', $t); + } + + public function test_should_support_text_values() + { + $e = array( + 'effect' => '$efname:100', + '$efname' => '!blur!', + ); + $t = Cloudinary::generate_transformation_string($e); + + $this->assertEquals('$efname_!blur!,e_$efname:100', $t); + } + + public function test_should_support_string_interpolation() + { + $this->cloudinary_url_assertion( + "sample", + array( + 'crop' => 'scale', + 'overlay' => array( + 'text' => '$(start)Hello $(name)$(ext), $(no ) $( no)$(end)', + 'font_family' => "Arial", + 'font_size' => "18", + ), + ), + CloudinaryTest::DEFAULT_UPLOAD_PATH . 'c_scale,l_text:Arial_18:$(start)Hello%20$(name)$(ext)%252C%20%24%28no%20%29%20%24%28%20no%29$(end)/sample' + ); + } + + /** + * Test build_array_of_assoc_arrays function + */ + public function test_build_array_of_assoc_arrays() + { + $assoc_array_data = array("one" => 1, "two" => 2, "three" => 3); + $array_of_assoc_array = array($assoc_array_data); + $method = new ReflectionMethod('Cloudinary', 'build_array_of_assoc_arrays'); + $method->setAccessible(true); + # should convert an assoc array to an array of assoc arrays + $this->assertEquals(array($assoc_array_data), $method->invoke(null, $assoc_array_data)); + + # should leave as is + $this->assertEquals($array_of_assoc_array, $method->invoke(null, $array_of_assoc_array)); + + # should convert a JSON string representing an assoc array to an array of assoc arrays + $string_data = '{"one": 1, "two": 2, "three": 3}'; + $this->assertEquals($array_of_assoc_array, $method->invoke(null, $string_data)); + + # should convert a JSON string representing an array of assoc arrays to an array of assoc arrays + $string_array_data = '[{"one": 1, "two": 2, "three": 3}]'; + $this->assertEquals($array_of_assoc_array, $method->invoke(null, $string_array_data)); + + # should return an empty array on null + $this->assertEquals(array(), $method->invoke(null, null)); + + # should return an empty array on array() + $this->assertEquals(array(), $method->invoke(null, array())); + + # should throw InvalidArgumentException on invalid value + $invalid_values = array("", array(array()), array("not_an_array"), array(7357)); + foreach ($invalid_values as $value) { + try { + $method->invoke(null, $value); + $this->fail('InvalidArgumentException was not thrown'); + } catch (\InvalidArgumentException $e) { + } + } + } + + /** + * Test json_encode_array_of_assoc_arrays function + */ + public function test_json_encode_array_of_assoc_arrays() + { + $method = new ReflectionMethod('Cloudinary', 'json_encode_array_of_assoc_arrays'); + $method->setAccessible(true); + # should encode simple values + $this->assertEquals('[]', $method->invoke(null, (array()))); + $this->assertEquals('[{"k":"v"}]', $method->invoke(null, array(array("k" =>"v")))); + + # should encode DateTime to ISO format + $this->assertEquals( + '[{"k":"2019-02-22T00:00:00+0000"}]', + $method->invoke(null, array(array("k" =>new \DateTime("2019-02-22")))) + ); + $this->assertEquals( + '[{"k":"2019-02-22T16:20:57+0000"}]', + $method->invoke(null, array(array("k" =>new \DateTime("2019-02-22 16:20:57Z")))) + ); + + # should throw InvalidArgumentException on invalid value + try { + $method->invoke(null, "not_valid"); + $this->fail('InvalidArgumentException was not thrown'); + } catch (\InvalidArgumentException $e) { + } + } + + /** + * Test encode_array_to_json function + * + * @see test_json_encode_array_of_assoc_arrays + * @see test_build_array_of_assoc_arrays + */ + public function test_encode_array_to_json() + { + $method = new ReflectionMethod('Cloudinary', 'json_encode_array_of_assoc_arrays'); + $method->setAccessible(true); + # should handle null value + $this->assertEquals(null, Cloudinary::encode_array_to_json(null)); + + # should handle regular case + $this->assertEquals('[{"k":"v"}]', Cloudinary::encode_array_to_json('[{"k":"v"}]')); + $this->assertEquals('[{"k":"v"}]', $method->invoke(null, array(array("k" =>"v")))); + } + + /** + * Should safely encode string to base64url format (with _ instead of / and - instead of +) + * + * @throws ReflectionException + */ + public function test_base64url_encode() + { + $base64url_encode = new ReflectionMethod('Cloudinary', 'base64url_encode'); + $base64url_encode->setAccessible(true); + + $this->assertEquals("YWQ_Lix4MDl-IUAh", $base64url_encode->invoke(null, "ad?.,x09~!@!")); + } + + /** + * Test array_copy function + */ + public function test_array_copy() + { + // Should return non array values as is + $this->assertEquals(null, Cloudinary::array_copy(null)); + $this->assertEquals('null', Cloudinary::array_copy('null')); + + // Should copy simple array + $orig_array = array('a', array('b' =>'c'), 'd'); + $same_orig_array = array('a', array('b' =>'c'), 'd'); + $copied_array = Cloudinary::array_copy($orig_array); + $orig_array[1]['b'] = 'e'; + + $this->assertNotEquals($same_orig_array, $orig_array); + $this->assertEquals($same_orig_array, $copied_array); + + // Should copy objects in an array + $o = new stdClass(); + $o->key = 'original_value'; + + $orig_array = array('o' =>$o); + + $shallow_copied_array = $orig_array; + $copied_array = Cloudinary::array_copy($orig_array); + + $o->key = 'new_value'; + + $this->assertEquals('new_value', $orig_array['o']->key); + $this->assertEquals('new_value', $shallow_copied_array['o']->key); + $this->assertEquals('original_value', $copied_array['o']->key); + } + + /** + * Should correctly handle format and fetch_format with and without custom transformation + */ + public function test_cloudinary_scaled_url() + { + $image_format = "jpg"; + $fetch_format = "gif"; + $resp_w = 99; + $resp_trans = "c_scale,w_$resp_w"; + $effect = "sepia"; + + $options = array("format" => $image_format, "type" => "fetch", "fetch_format" => $fetch_format); + + // Without custom transformation + $actual_url = Cloudinary::cloudinary_scaled_url(self::FETCH_URL, $resp_w, [], $options); + + $this->assertEquals( + self::DEFAULT_FETCH_PATH . "f_$fetch_format/$resp_trans/" . self::FETCH_URL, + $actual_url + ); + + // With custom transformation + $actual_url = Cloudinary::cloudinary_scaled_url(self::FETCH_URL, $resp_w, self::$crop_transformation, $options); + + $this->assertEquals( + self::DEFAULT_FETCH_PATH . "c_crop,f_$image_format,w_100/$resp_trans/" . self::FETCH_URL, + $actual_url + ); + + // Add base transformation + $options["effect"] = $effect; + $actual_url = Cloudinary::cloudinary_scaled_url(self::FETCH_URL, $resp_w, [], $options); + + $this->assertEquals( + self::DEFAULT_FETCH_PATH . "e_$effect,f_$fetch_format/$resp_trans/" . self::FETCH_URL, + $actual_url + ); + + // Should ignore base transformation + $actual_url = Cloudinary::cloudinary_scaled_url(self::FETCH_URL, $resp_w, self::$crop_transformation, $options); + + $this->assertEquals( + self::DEFAULT_FETCH_PATH . "c_crop,f_$image_format,w_100/$resp_trans/" . self::FETCH_URL, + $actual_url + ); + + $options["raw_transformation"] = self::$raw_transformation; + + // Should include raw transformation from base options + $actual_url = Cloudinary::cloudinary_scaled_url(self::FETCH_URL, $resp_w, [], $options); + + $this->assertEquals( + self::DEFAULT_FETCH_PATH . "e_$effect,f_$fetch_format," . self::$raw_transformation. "/$resp_trans/". + self::FETCH_URL, + $actual_url + ); + } + + public function test_build_eager() + { + $test_data = [ + ['should support strings', + [self::$sepia_transformation_str, self::$sepia_transformation_str . '/jpg'], + self::$sepia_transformation_str . '|' . self::$sepia_transformation_str . '/jpg'], + ['should concatenate transformations using pipe', + [self::$crop_transformation, self::$sepia_transformation], + self::$crop_transformation_str . '|' . self::$sepia_transformation_str], + ['should support transformations with multiple components', + [['transformation' => [self::$crop_transformation, self::$sepia_transformation]], + self::$sepia_transformation], + self::$crop_transformation_str . '/' . self::$sepia_transformation_str . '|' . + self::$sepia_transformation_str], + ['should concatenate format at the end of the transformation', + [array_merge(self::$crop_transformation, ['format' => 'gif']), self::$sepia_transformation], + self::$crop_transformation_str . '/gif|' . self::$sepia_transformation_str], + ['should support an empty format', + [array_merge(self::$crop_transformation, ['format' => '']), self::$sepia_transformation], + self::$crop_transformation_str . '/|' . self::$sepia_transformation_str], + ['should treat a null format as none', + [array_merge(self::$crop_transformation, ['format' => null]), self::$sepia_transformation], + self::$crop_transformation_str . '|' . self::$sepia_transformation_str], + ['should concatenate format at the end of the transformation', + [array_merge(self::$crop_transformation, ['format' => 'gif']), + array_merge(self::$sepia_transformation, ['format' => 'jpg'])], + self::$crop_transformation_str . '/gif|' . self::$sepia_transformation_str . '/jpg'], + ['should support transformations with multiple components and format', + [['transformation' => [self::$crop_transformation, self::$sepia_transformation], 'format' => 'gif'], + self::$sepia_transformation], + self::$crop_transformation_str . '/' . self::$sepia_transformation_str . '/gif|' . + self::$sepia_transformation_str] + ]; + + foreach ($test_data as $single_test) { + $this->assertEquals($single_test[2], Cloudinary::build_eager($single_test[1]), $single_test[0]); + } + } + + private function cloudinary_url_assertion($source, $options, $expected, $expected_options = array()) + { + $url = Cloudinary::cloudinary_url($source, $options); + $this->assertEquals($expected_options, $options); + $this->assertEquals($expected, $url); + } + +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/ConfigTest.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/ConfigTest.php new file mode 100644 index 0000000..e527de8 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/ConfigTest.php @@ -0,0 +1,42 @@ +assertArrayHasKey('foo', \Cloudinary::config()); + $this->assertArrayHasKey('bar', \Cloudinary::config()['foo']); + $this->assertEquals('value', \Cloudinary::config()['foo']['bar']); + } + + public function test_cloudinary_url_valid_scheme() + { + $cloudinary_url = 'cloudinary://123456789012345:ALKJdjklLJAjhkKJ45hBK92baj3@test'; + + \Cloudinary::config_from_url($cloudinary_url); + } + + public function test_cloudinary_url_invalid_scheme() + { + $cloudinary_urls = [ + 'CLOUDINARY_URL=cloudinary://123456789012345:ALKJdjklLJAjhkKJ45hBK92baj3@test', + 'https://123456789012345:ALKJdjklLJAjhkKJ45hBK92baj3@test', + '://123456789012345:ALKJdjklLJAjhkKJ45hBK92baj3@test', + ' ' + ]; + + foreach ($cloudinary_urls as $cloudinary_url) { + try { + \Cloudinary::config_from_url($cloudinary_url); + $this->fail('InvalidArgumentException was not thrown'); + } catch (\InvalidArgumentException $e) { + } + } + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/HelpersTest.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/HelpersTest.php new file mode 100644 index 0000000..bf792f2 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/HelpersTest.php @@ -0,0 +1,106 @@ + 'crop', 'width' => 100]; + protected static $crop_transformation_str = 'c_crop,w_100'; + + + public static function setUpBeforeClass() + { + \Cloudinary::reset_config(); + + if (!Cloudinary::config_get("api_secret")) { + self::markTestSkipped('Please setup environment for Helpers test to run'); + } + + self::$helpers_test_id = "helpers_test_" . UNIQUE_TEST_ID; + + Uploader::upload(TEST_IMG, ["public_id" => self::$helpers_test_id, "tags" => array(TEST_TAG, UNIQUE_TEST_TAG)]); + } + + public static function tearDownAfterClass() + { + if (!Cloudinary::config_get("api_secret")) { + self::fail("You need to configure the cloudinary api for the tests to work."); + } + + $api = new Cloudinary\Api(); + + try { + $api->delete_resources([self::$helpers_test_id]); + } catch (\Exception $e) { + } + } + + public function setUp() + { + Curl::$instance = new Curl(); + } + + /** + * Should retrieve responsive breakpoints from cloudinary resource (mocked) + * + * @throws \Cloudinary\Error + */ + public function test_fetch_breakpoints() + { + Curl::mockRequest($this, self::$mocked_response); + + $actual_breakpoints = fetch_breakpoints(self::$helpers_test_id); + + $this->assertEquals(self::$mocked_breakpoints, $actual_breakpoints); + + $this->assertContains(self::$expected_transformation, Curl::$instance->url_path()); + } + + /** + * Should retrieve responsive breakpoints from cloudinary resource with custom transformation (mocked) + * + * @throws \Cloudinary\Error + */ + public function test_fetch_breakpoints_with_transformation() + { + Curl::mockRequest($this, self::$mocked_response); + + $srcset = ["transformation" => self::$crop_transformation]; + $actual_breakpoints = fetch_breakpoints(self::$helpers_test_id, $srcset); + + $this->assertEquals(self::$mocked_breakpoints, $actual_breakpoints); + + $this->assertContains( + self::$crop_transformation_str . '/' .self::$expected_transformation, + Curl::$instance->url_path() + ); + } + + /** + * Should retrieve responsive breakpoints from cloudinary resource (real request) + * + * @throws \Cloudinary\Error + */ + public function test_fetch_breakpoints_real() + { + $actual_breakpoints = fetch_breakpoints(self::$helpers_test_id); + + $this->assertContains(self::$expected_transformation, Curl::$instance->url_path()); + + $this->assertTrue(is_array($actual_breakpoints)); + $this->assertGreaterThan(0, count($actual_breakpoints)); + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/HttpClientTest.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/HttpClientTest.php new file mode 100644 index 0000000..fb08500 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/HttpClientTest.php @@ -0,0 +1,104 @@ + self::$http_client_test_id]); + } + + public static function tearDownAfterClass() + { + if (!Cloudinary::config_get("api_secret")) { + self::fail("You need to configure the cloudinary api for the tests to work."); + } + + $api = new Cloudinary\Api(); + + try { + $api->delete_resources([self::$http_client_test_id]); + } catch (\Exception $e) { + } + } + + public function setUp() + { + $this->httpClient = new HttpClient(); + } + + + /** + * @throws Error + */ + public function testHttpClientGetJSON() + { + $json_options = ['width'=> 'auto:breakpoints:json']; + $json_url = Cloudinary::cloudinary_url(self::$http_client_test_id, $json_options); + $json = $this->httpClient->getJSON($json_url); + + $this->assertArrayHasKey('breakpoints', $json); + $this->assertTrue(is_array($json['breakpoints'])); + } + + /** + * Should throw Cloudinary\Error on invalid or non JSON reponse + */ + public function testHttpClientGetJSONNonJSON() + { + $url = Cloudinary::cloudinary_url(self::$http_client_test_id); + + $message = ""; + + try { + $this->httpClient->getJSON($url); + $this->fail("Cloudinary\Error expected"); + } catch (Cloudinary\Error $e) { + $message = $e->getMessage(); + } + + self::assertStringStartsWith("Error parsing server response", $message); + } + + /** + * Should throw Cloudinary\Error on invalid or non existing URL + */ + public function testHttpClientGetJSONInvalidURL() + { + $url = Cloudinary::cloudinary_url(self::$http_client_test_id . '_non_existing'); + + $message = ""; + + try { + $this->httpClient->getJSON($url); + $this->fail("Cloudinary\Error expected"); + } catch (Cloudinary\Error $e) { + $message = $e->getMessage(); + } + + self::assertStringStartsWith("Server returned unexpected status code", $message); + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/SearchTest.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/SearchTest.php new file mode 100644 index 0000000..ca50e59 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/SearchTest.php @@ -0,0 +1,175 @@ + UNIQUE_TEST_TAG . "_" . $i, + "tags" => array(TEST_TAG, UNIQUE_TEST_TAG), + "context" => "stage=value", + "eager" => array( + "transformation" => array( + "width" => 100, + "crop" => "scale", + ), + ), + ) + ); + } + sleep(3); + } + + public function setUp() + { + \Cloudinary::reset_config(); + if (!\Cloudinary::config_get("api_secret")) { + $this->markTestSkipped("Please setup environment for Search test to run"); + } + $this->search = new Search(); + } + + public function tearDown() + { + Curl::$instance = new Curl(); + } + + public static function tearDownAfterClass() + { + Curl::$instance = new Curl(); + (new Api())->delete_resources_by_tag(UNIQUE_TEST_TAG); + } + + public function test_empty_query() + { + $result = $this->search->as_array(); + $this->assertEquals(count($result), 0, "Should generate an empty query JSON"); + } + + public function test_should_add_expression_as_array() + { + + $query = $this->search->expression('format:jpg')->as_array(); + $this->assertEquals($query, array("expression" => 'format:jpg')); + } + + public function test_should_add_sort_by_as_array() + { + $query = $this->search->sort_by('created_at', 'asc')->sort_by('updated_at', 'desc')->as_array(); + $this->assertEquals( + $query, + array( + "sort_by" => array( + array('created_at' => 'asc'), + array('updated_at' => 'desc'), + ), + ) + ); + } + + public function test_should_add_max_results_as_array() + { + $query = $this->search->max_results('10')->as_array(); + $this->assertEquals($query, array("max_results" => '10')); + } + + public function test_should_add_next_cursor_as_array() + { + + $query = $this + ->search + ->next_cursor('ec471a97ba510904ab57460b3ba3150ec29b6f8563eb1c10f6925ed0c6813f33cfa62ec6cf5ad96be6d6fa3ac3a76ccb') + ->as_array(); + + $this->assertEquals( + $query, + array("next_cursor" => 'ec471a97ba510904ab57460b3ba3150ec29b6f8563eb1c10f6925ed0c6813f33cfa62ec6cf5ad96be6d6fa3ac3a76ccb') + ); + } + + public function test_should_add_aggregations_arguments_as_array_as_array() + { + $query = $this->search->aggregate('format')->aggregate('size_category')->as_array(); + $this->assertEquals($query, array("aggregate" => ["format", "size_category"])); + } + + public function test_should_add_with_field_as_array() + { + $query = $this->search->with_field('context')->with_field('tags')->as_array(); + $this->assertEquals($query, array("with_field" => ["context", "tags"])); + } + + public function test_should_return_all_images_tagged() + { + + $results = $this->search->expression("tags:" . UNIQUE_TEST_TAG)->execute(); + $this->assertEquals(count($results['resources']), 3); + } + + public function test_should_return_resource() + { + $results = $this->search->expression("public_id:" . UNIQUE_TEST_TAG . "_1")->execute(); + $this->assertEquals(count($results['resources']), 1); + } + + public function test_execute_with_params() + { + Curl::mockApi($this); + $result = $this + ->search + ->expression("format:jpg") + ->max_results(10) + ->next_cursor("abcd") + ->sort_by("created_at", "asc") + ->sort_by("updated_at") + ->aggregate("format") + ->aggregate("resource_type") + ->with_field("tags") + ->with_field("image_metadata") + ->execute(); + + assertJson( + $this, + json_encode( + array( + "sort_by" => array( + array("created_at" => "asc"), + array("updated_at" => "desc"), + ), + "aggregate" => array("format", "resource_type"), + "with_field" => array("tags", "image_metadata"), + "expression" => "format:jpg", + "max_results" => 10, + "next_cursor" => "abcd", + ) + ), + Curl::$instance->fields(), "Should correctly encode JSON into the HTTP request" + ); + + assertJson( + $this, + json_encode(array("Content-type: application/json", "Accept: application/json")), + json_encode(Curl::$instance->getopt(CURLOPT_HTTPHEADER)), + "Should use right headers for execution of advanced search api" + ); + } + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/SignatureVerifierTest.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/SignatureVerifierTest.php new file mode 100644 index 0000000..01c1991 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/SignatureVerifierTest.php @@ -0,0 +1,231 @@ + self::API_SECRET]); + } + + public function tearDown() + { + \Cloudinary::reset_config(); + } + + public function testSuccessfulNotificationSignatureVerification() + { + $signatureTestValues = [ + ['timestamp' => self::$timestamp, 'validFor' => self::$validFor], + ['timestamp' => (string)self::$timestamp, 'validFor' => (string)self::$validFor] + ]; + + foreach ($signatureTestValues as $value) { + $result = SignatureVerifier::verifyNotificationSignature( + self::$notificationBody, + $value['timestamp'], + self::$notificationSignature, + $value['validFor'] + ); + + self::assertTrue($result); + } + } + + public function testFailedNotificationSignatureVerification() + { + $signatureTestValues = [ + ['body' => '{' . self::$notificationBody . '}', 'timestamp' => self::$timestamp, + 'signature' => self::$notificationSignature], + ['body' => self::$notificationBody, 'timestamp' => self::$timestamp - 1, + 'signature' => self::$notificationSignature], + ['body' => self::$notificationBody, 'timestamp' => self::$timestamp, + 'signature' => self::$notificationSignature . 'a'] + ]; + + foreach ($signatureTestValues as $value) { + $result = SignatureVerifier::verifyNotificationSignature( + $value['body'], + $value['timestamp'], + $value['signature'] + ); + + self::assertFalse($result); + } + } + + public function testDefaultValidFor() + { + $result = SignatureVerifier::verifyNotificationSignature( + self::$notificationBody, + self::$timestamp, + self::$notificationSignature + ); + + self::assertTrue($result); + } + + public function testExpiredTimestamp() + { + $reducedValidFor = self::$validFor - 1; + + $result = SignatureVerifier::verifyNotificationSignature( + self::$notificationBody, + self::$timestamp, + self::$notificationSignature, + $reducedValidFor + ); + + self::assertFalse($result); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage API Secret is invalid + */ + public function testNotificationMissingApiSecret() + { + \Cloudinary::config(['api_secret' => null]); + + SignatureVerifier::verifyNotificationSignature( + self::$notificationBody, + self::$timestamp, + self::$notificationSignature + ); + } + + public function testNotificationInvalidParameter() + { + $invalidValues = [ + ['body' => null, 'timestamp' => self::$timestamp, + 'signature' => self::$notificationSignature], + ['body' => self::$notificationBody, 'timestamp' => null, + 'signature' => self::$notificationSignature], + ['body' => self::$notificationBody, 'timestamp' => self::$timestamp, + 'signature' => null], + ['body' => [self::$notificationBody], 'timestamp' => self::$timestamp, + 'signature' => self::$notificationSignature], + ['body' => self::$notificationBody, 'timestamp' => [self::$timestamp], + 'signature' => self::$notificationSignature], + ['body' => self::$notificationBody, 'timestamp' => self::$timestamp, + 'signature' => [self::$notificationSignature]] + ]; + + foreach ($invalidValues as $value) { + $success = false; + try { + SignatureVerifier::verifyNotificationSignature( + $value['body'], + $value['timestamp'], + $value['signature'] + ); + } catch (\InvalidArgumentException $e) { + $success = true; + } + self::assertTrue($success); + } + } + + public function testSuccessfulApiResponseSignatureVerification() + { + $signatureTestValues = [self::TEST_VERSION, (string)self::TEST_VERSION]; + + foreach ($signatureTestValues as $value) { + $result = SignatureVerifier::verifyApiResponseSignature( + self::$publicId, + $value, + self::$apiResponseSignature + ); + + self::assertTrue($result); + } + } + + public function testFailedApiResponseSignatureVerification() + { + $signatureTestValues = [ + ['publicId' => self::$publicId . 'a', 'version' => self::TEST_VERSION, + 'signature' => self::$apiResponseSignature], + ['publicId' => self::$publicId, 'version' => self::TEST_VERSION + 1, + 'signature' => self::$apiResponseSignature], + ['publicId' => self::$publicId, 'version' => self::TEST_VERSION, + 'signature' => self::$apiResponseSignature . 'a'] + ]; + + foreach ($signatureTestValues as $value) { + $result = SignatureVerifier::verifyApiResponseSignature( + $value['publicId'], + $value['version'], + $value['signature'] + ); + + self::assertFalse($result); + } + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage API Secret is invalid + */ + public function testApiResponseMissingApiSecret() + { + \Cloudinary::config(['api_secret' => null]); + + SignatureVerifier::verifyApiResponseSignature( + self::$publicId, + self::TEST_VERSION, + self::$apiResponseSignature + ); + } +} + +namespace Cloudinary; + +/** + * Mock for time() function + * + * @return int With test timestamp associated with the signature in the notifications test + */ +function time() +{ + return Test\SignatureVerifierTest::$mockedNow ?: \time(); +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/TagTest.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/TagTest.php new file mode 100644 index 0000000..bcd5cc9 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/TagTest.php @@ -0,0 +1,1217 @@ + 'custom_value1', 'custom_attr2' => 'custom_value2'); + private static $common_image_options = array( + 'effect' => 'sepia', + 'cloud_name' => 'test123', + 'client_hints' => false, + ); + private static $fill_transformation; + private static $fill_trans_str; + private static $common_srcset; + private static $breakpoints_arr; + private static $sizes_attr; + + public static function setUpBeforeClass() + { + self::$breakpoints_arr = array(self::$min_width, 200, 300, self::$max_width); + self::$max_images = count(self::$breakpoints_arr); + self::$common_srcset = array('breakpoints' => self::$breakpoints_arr); + + self::$fill_transformation = ['width' => self::$max_width, 'height' => self::$max_width, 'crop' => 'fill']; + self::$fill_trans_str = "c_fill,h_" . self::$max_width . ",w_" . self::$max_width; + + self::$sizes_attr = implode( + ', ', + array_map( + function ($w) { + return "(max-width: ${w}px) ${w}px"; + }, + self::$breakpoints_arr + ) + ); + + Curl::$instance = new Curl(); + } + + public function setUp() + { + Cloudinary::reset_config(); + Cloudinary::config( + array( + "cloud_name" => "test123", + "api_key" => "a", + "api_secret" => "b", + "secure_distribution" => null, + "private_cdn" => false, + "cname" => null + ) + ); + } + + public function test_cl_image_tag() + { + $tag = cl_image_tag("test", array("width" => 10, "height" => 10, "crop" => "fill", "format" => "png")); + $this->assertEquals( + "", + $tag + ); + } + + /** + * Should create a meta tag with client hints + */ + public function test_cl_client_hints_meta_tag() + { + $doc = new DOMDocument(); + $doc->loadHTML(cl_client_hints_meta_tag()); + $tags = $doc->getElementsByTagName('meta'); + $this->assertEquals($tags->length, 1); + $this->assertEquals($tags->item(0)->getAttribute('content'), 'DPR, Viewport-Width, Width'); + $this->assertEquals($tags->item(0)->getAttribute('http-equiv'), 'Accept-CH'); + } + + /** + * Check that cl_image_tag encodes special characters. + */ + public function test_cl_image_tag_special_characters_encoding() + { + $tag = cl_image_tag( + "test's special < \"characters\" >", + array("width" => 10, "height" => 10, "crop" => "fill", "format" => "png", "alt" => "< test's > special \"") + ); + $expected = "< test's > special ""; + + $this->assertEquals($expected, $tag); + } + + public function test_responsive_width() + { + // should add responsive width transformation + $tag = cl_image_tag("hello", array("responsive_width" => true, "format" => "png")); + $this->assertEquals( + "", + $tag + ); + + $options = array("width" => 100, "height" => 100, "crop" => "crop", "responsive_width" => true); + $result = Cloudinary::cloudinary_url("test", $options); + $this->assertEquals($options, array("responsive" => true)); + $this->assertEquals($result, TagTest::DEFAULT_UPLOAD_PATH . "c_crop,h_100,w_100/c_limit,w_auto/test"); + Cloudinary::config( + array( + "responsive_width_transformation" => array( + "width" => "auto:breakpoints", + "crop" => "pad", + ), + ) + ); + $options = array("width" => 100, "height" => 100, "crop" => "crop", "responsive_width" => true); + $result = Cloudinary::cloudinary_url("test", $options); + $this->assertEquals($options, array("responsive" => true)); + $this->assertEquals( + $result, + TagTest::DEFAULT_UPLOAD_PATH . "c_crop,h_100,w_100/c_pad,w_auto:breakpoints/test" + ); + } + + public function test_width_auto() + { + // should support width=auto + $tag = cl_image_tag("hello", array("width" => "auto", "crop" => "limit", "format" => "png")); + $this->assertEquals( + "", + $tag + ); + $tag = cl_image_tag("hello", array("width" => "auto:breakpoints", "crop" => "limit", "format" => "png")); + $this->assertEquals( + "", + $tag + ); + $this->cloudinary_url_assertion( + "test", + array("width" => "auto:20", "crop" => 'fill'), + TagTest::DEFAULT_UPLOAD_PATH . "c_fill,w_auto:20/test", + array('responsive' => true) + ); + $this->cloudinary_url_assertion( + "test", + array("width" => "auto:20:350", "crop" => 'fill'), + TagTest::DEFAULT_UPLOAD_PATH . "c_fill,w_auto:20:350/test", + array('responsive' => true) + ); + $this->cloudinary_url_assertion( + "test", + array("width" => "auto:breakpoints", "crop" => 'fill'), + TagTest::DEFAULT_UPLOAD_PATH . "c_fill,w_auto:breakpoints/test", + array('responsive' => true) + ); + $this->cloudinary_url_assertion( + "test", + array("width" => "auto:breakpoints_100_1900_20_15", "crop" => 'fill'), + TagTest::DEFAULT_UPLOAD_PATH . "c_fill,w_auto:breakpoints_100_1900_20_15/test", + array('responsive' => true) + ); + $this->cloudinary_url_assertion( + "test", + array("width" => "auto:breakpoints:json", "crop" => 'fill'), + TagTest::DEFAULT_UPLOAD_PATH . "c_fill,w_auto:breakpoints:json/test", + array('responsive' => true) + ); + } + + public function test_initial_width_and_height() + { + $options = array("crop" => "crop", "width" => "iw", "height" => "ih"); + $this->cloudinary_url_assertion( + "test", + $options, + TagTest::DEFAULT_UPLOAD_PATH . "c_crop,h_ih,w_iw/test" + ); + } + + /** + * @param $options + * @param string $message + */ + public function shared_client_hints($options, $message = '') + { + $tag = cl_image_tag('sample.jpg', $options); + $this->assertEquals( + "", + $tag, + $message + ); + $tag = cl_image_tag('sample.jpg', array_merge(array("responsive" => true), $options)); + $this->assertEquals( + "", + $tag, + $message + ); + } + + public function test_client_hints_as_option() + { + $this->shared_client_hints( + array( + "dpr" => "auto", + "cloud_name" => "test", + "width" => "auto", + "crop" => "scale", + "client_hints" => true, + ), + "support client_hints as an option" + ); + } + + public function test_client_hints_as_global() + { + Cloudinary::config(array("client_hints" => true)); + $this->shared_client_hints( + array( + "dpr" => "auto", + "cloud_name" => "test", + "width" => "auto", + "crop" => "scale", + ), + "support client hints as global configuration" + ); + } + + public function test_client_hints_false() + { + Cloudinary::config(array("responsive" => true)); + $tag = cl_image_tag( + 'sample.jpg', + array( + "width" => "auto", + "crop" => "scale", + "cloud_name" => "test123", + "client_hints" => false, + ) + ); + $this->assertEquals( + "", + $tag, + "should use normal responsive behaviour" + ); + } + + /** + * @internal + * Helper method for generating expected `img` and `source` tags + * + * @param string $tag_name Expected tag name(img or source) + * @param string $public_id Public ID of the image + * @param string $common_trans_str Default transformation string to be used in all resources + * @param string $custom_trans_str Optional custom transformation string to be be used inside srcset resources + * If not provided, $common_trans_str is used + * @param array $srcset_breakpoints Optional list of breakpoints for srcset. If not provided srcset is omitted + * @param array $attributes Associative array of custom attributes to be added to the tag + * + * @param bool $is_void Indicates whether tag is an HTML5 void tag (does not need to be self-closed) + * + * @return string Resulting tag + */ + private function common_image_tag_helper( + $tag_name, + $public_id, + $common_trans_str, + $custom_trans_str = '', + $srcset_breakpoints = array(), + $attributes = array(), + $is_void = false + ) { + if (empty($custom_trans_str)) { + $custom_trans_str = $common_trans_str; + } + + if (!empty($srcset_breakpoints)) { + $single_srcset_image = function ($w) use ($custom_trans_str, $public_id) { + return self::DEFAULT_UPLOAD_PATH . "{$custom_trans_str}/c_scale,w_{$w}/{$public_id} {$w}w"; + }; + $attributes['srcset'] = implode(', ', array_map($single_srcset_image, $srcset_breakpoints)); + } + + $tag = "<$tag_name"; + + $attributes_str = implode( + ' ', + array_map( + function ($k, $v) { + return "$k='$v'"; + }, + array_keys($attributes), + array_values($attributes) + ) + ); + + if (!empty($attributes_str)) { + $tag .= " {$attributes_str}"; + } + + $tag .= $is_void ? ">" : "/>"; // HTML5 void elements do not need to be self closed + + if (getenv('DEBUG')) { + echo preg_replace('/([,\']) /', "$1\n ", $tag) . "\n\n"; + } + + return $tag; + } + + /** + * @internal + * Helper method for test_cl_image_tag_srcset for generating expected image tag + * + * @param string $public_id Public ID of the image + * @param string $common_trans_str Default transformation string to be used in all resources + * @param string $custom_trans_str Optional custom transformation string to be be used inside srcset resources + * If not provided, $common_trans_str is used + * @param array $srcset_breakpoints Optional list of breakpoints for srcset. If not provided srcset is omitted + * @param array $attributes Associative array of custom attributes to be added to the tag + * + * @return string Resulting image tag + */ + private function get_expected_cl_image_tag( + $public_id, + $common_trans_str, + $custom_trans_str = '', + $srcset_breakpoints = array(), + $attributes = array() + ) { + + Cloudinary::array_unshift_assoc( + $attributes, + 'src', + self::DEFAULT_UPLOAD_PATH . "{$common_trans_str}/{$public_id}" + ); + + return $this->common_image_tag_helper( + "img", + $public_id, + $common_trans_str, + $custom_trans_str, + $srcset_breakpoints, + $attributes + ); + } + + /** + * @internal + * Helper method for for generating expected `source` tag + * + * @param string $public_id Public ID of the image + * @param string $common_trans_str Default transformation string to be used in all resources + * @param string $custom_trans_str Optional custom transformation string to be be used inside srcset resources + * If not provided, $common_trans_str is used + * @param array $srcset_breakpoints Optional list of breakpoints for srcset. If not provided srcset is omitted + * @param array $attributes Associative array of custom attributes to be added to the tag + * + * @return string Resulting `source` tag + */ + private function get_expected_cl_source_tag( + $public_id, + $common_trans_str, + $custom_trans_str = '', + $srcset_breakpoints = array(), + $attributes = array() + ) { + + $attributes['srcset'] = self::DEFAULT_UPLOAD_PATH . "{$common_trans_str}/{$public_id }"; + + ksort($attributes); // Used here to produce output similar to Cloudinary::html_attrs + + return $this->common_image_tag_helper( + "source", + $public_id, + $common_trans_str, + $custom_trans_str, + $srcset_breakpoints, + $attributes, + true + ); + } + + /** + * Should create srcset attribute with provided breakpoints + */ + public function test_cl_image_tag_srcset() + { + $expected_tag = self::get_expected_cl_image_tag( + self::$public_id, + self::$common_transformation_str, + '', + self::$breakpoints_arr + ); + + $tag_with_breakpoints = cl_image_tag( + self::$public_id, + array_merge( + self::$common_image_options, + array('srcset' => self::$common_srcset) + ) + ); + + $this->assertEquals( + $expected_tag, + $tag_with_breakpoints, + 'Should create img srcset attribute with provided breakpoints' + ); + } + + public function test_support_srcset_attribute_defined_by_min_width_max_width_and_max_images() + { + $tag_min_max_count = cl_image_tag( + self::$public_id, + array_merge( + self::$common_image_options, + array( + 'srcset' => array( + 'min_width' => self::$min_width, + 'max_width' => $x = self::$max_width, + 'max_images' => count(self::$breakpoints_arr) + ) + ) + ) + ); + + $expected_tag = self::get_expected_cl_image_tag( + self::$public_id, + self::$common_transformation_str, + '', + self::$breakpoints_arr + ); + + $this->assertEquals( + $expected_tag, + $tag_min_max_count, + 'Should support srcset attribute defined by min_width, max_width, and max_images' + ); + + // Should support 1 image in srcset + $tag_one_image_by_params = cl_image_tag( + self::$public_id, + array_merge( + self::$common_image_options, + array( + 'srcset' => array( + 'min_width' => self::$breakpoints_arr[0], + 'max_width' => self::$max_width, + 'max_images' => 1 + ) + ) + ) + ); + + $expected_1_image_tag = self::get_expected_cl_image_tag( + self::$public_id, + self::$common_transformation_str, + '', + array(self::$max_width) + ); + + $this->assertEquals($expected_1_image_tag, $tag_one_image_by_params); + + $tag_one_image_by_breakpoints = cl_image_tag( + self::$public_id, + array_merge( + self::$common_image_options, + array('srcset' => array('breakpoints' => array(self::$max_width))) + ) + ); + $this->assertEquals($expected_1_image_tag, $tag_one_image_by_breakpoints); + + // Should support custom transformation for srcset items + $custom_transformation = array("transformation" => array("crop" => "crop", "width" => 10, "height" => 20)); + + $tag_custom_transformation = cl_image_tag( + self::$public_id, + array_merge( + self::$common_image_options, + array( + 'srcset' => array_merge( + self::$common_srcset, + $custom_transformation + ) + ) + ) + ); + + $custom_transformation_str = 'c_crop,h_20,w_10'; + $custom_expected_tag = self::get_expected_cl_image_tag( + self::$public_id, + self::$common_transformation_str, + $custom_transformation_str, + self::$breakpoints_arr + ); + + $this->assertEquals($custom_expected_tag, $tag_custom_transformation); + + // Should populate sizes attribute + $tag_with_sizes = cl_image_tag( + self::$public_id, + array_merge( + self::$common_image_options, + array( + 'srcset' => array_merge( + self::$common_srcset, + array('sizes' => true) + ) + ) + ) + ); + + $expected_tag_with_sizes = self::get_expected_cl_image_tag( + self::$public_id, + self::$common_transformation_str, + '', + self::$breakpoints_arr, + array('sizes' => self::$sizes_attr) + ); + $this->assertEquals($expected_tag_with_sizes, $tag_with_sizes); + + // Should support srcset string value + $raw_srcset_value = "some srcset data as is"; + $tag_with_raw_srcset = cl_image_tag( + self::$public_id, + array_merge( + self::$common_image_options, + array('attributes' => array('srcset' => $raw_srcset_value)) + ) + ); + + $expected_raw_srcset = self::get_expected_cl_image_tag( + self::$public_id, + self::$common_transformation_str, + '', + array(), + array('srcset' => $raw_srcset_value) + ); + + $this->assertEquals($expected_raw_srcset, $tag_with_raw_srcset); + + // Should remove width and height attributes in case srcset is specified, but passed to transformation + $tag_with_sizes = cl_image_tag( + self::$public_id, + array_merge( + array_merge( + self::$common_image_options, + array('width' => 500, 'height' => 500) + ), + array('srcset' => self::$common_srcset) + ) + ); + + $expected_tag_without_width_and_height = self::get_expected_cl_image_tag( + self::$public_id, + 'e_sepia,h_500,w_500', + '', + self::$breakpoints_arr + ); + $this->assertEquals($expected_tag_without_width_and_height, $tag_with_sizes); + } + + /** + * Should omit srcset attribute on invalid values + * + * @throws \Exception + */ + public function test_srcset_invalid_values() + { + $invalid_breakpoints = array( + array('sizes' => true), // srcset data not provided + array('max_width' => 300, 'max_images' => 3), // no min_width + array('min_width' => '1', 'max_width' => 300, 'max_images' => 3), // invalid min_width + array('min_width' => 100, 'max_images' => 3), // no max_width + array('min_width' => '1', 'max_width' => '3', 'max_images' => 3), // invalid max_width + array('min_width' => 200, 'max_width' => 100, 'max_images' => 3), // min_width > max_width + array('min_width' => 100, 'max_width' => 300), // no max_images + array('min_width' => 100, 'max_width' => 300, 'max_images' => 0), // invalid max_images + array('min_width' => 100, 'max_width' => 300, 'max_images' => -17), // invalid max_images + array('min_width' => 100, 'max_width' => 300, 'max_images' => '3'), // invalid max_images + array('min_width' => 100, 'max_width' => 300, 'max_images' => null), // invalid max_images + ); + + + $err_log_original_destination = ini_get('error_log'); + // Suppress error messages in error log + ini_set('error_log', '/dev/null'); + + try { + foreach ($invalid_breakpoints as $value) { + $tag = cl_image_tag( + self::$public_id, + array_merge(self::$common_image_options, array('srcset' => $value)) + ); + + self::assertNotContains("srcset", $tag); + } + } catch (\Exception $e) { + ini_set('error_log', $err_log_original_destination); + throw $e; + } + + ini_set('error_log', $err_log_original_destination); + } + + public function test_cl_image_tag_responsive_breakpoints_cache() + { + $cache = ResponsiveBreakpointsCache::instance(); + $cache->setCacheAdapter(new KeyValueCacheAdapter(new DummyCacheStorage())); + + $cache->set(self::$public_id, self::$common_image_options, self::$breakpoints_arr); + + $expected_tag = self::get_expected_cl_image_tag( + self::$public_id, + self::$common_transformation_str, + '', + self::$breakpoints_arr + ); + + $image_tag = cl_image_tag( + self::$public_id, + array_merge( + self::$common_image_options, + ["srcset"=> ["use_cache" => true]] + ) + ); + + $this->assertEquals($expected_tag, $image_tag); + } + + public function test_create_a_tag_with_custom_attributes_legacy_approach() + { + $tag_with_custom_legacy_attribute = cl_image_tag( + self::$public_id, + array_merge( + self::$common_image_options, + self::$custom_attributes + ) + ); + + $expected_custom_attributes_tag = self::get_expected_cl_image_tag( + self::$public_id, + self::$common_transformation_str, + '', + array(), + self::$custom_attributes + ); + + $this->assertEquals($expected_custom_attributes_tag, $tag_with_custom_legacy_attribute); + } + + public function test_create_a_tag_with_legacy_srcset_attribute() + { + $srcset_attribute = ['srcset' =>'http://custom.srcset.attr/sample.jpg 100w']; + $tag_with_custom_srcset_attribute = cl_image_tag( + self::$public_id, + array_merge( + self::$common_image_options, + $srcset_attribute + ) + ); + + $expected_custom_attributes_tag = self::get_expected_cl_image_tag( + self::$public_id, + self::$common_transformation_str, + '', + array(), + $srcset_attribute + ); + + $this->assertEquals($expected_custom_attributes_tag, $tag_with_custom_srcset_attribute); + } + + + public function test_consume_custom_attributes_from_attributes_key() + { + $tag_with_custom_attribute = cl_image_tag( + self::$public_id, + array_merge( + self::$common_image_options, + array('attributes' => self::$custom_attributes) + ) + ); + $expected_custom_attributes_tag = self::get_expected_cl_image_tag( + self::$public_id, + self::$common_transformation_str, + '', + array(), + self::$custom_attributes + ); + $this->assertEquals($expected_custom_attributes_tag, $tag_with_custom_attribute); + } + + public function test_override_existing_attributes_with_specified_by_custom_ones() + { + $updated_attributes = array('alt' => 'updated alt'); + $tag_with_custom_overriden_attribute = cl_image_tag( + self::$public_id, + array_merge( + self::$common_image_options, + array('alt' => 'original alt', 'attributes' => $updated_attributes) + ) + ); + + $expected_overriden_attributes_tag = self::get_expected_cl_image_tag( + self::$public_id, + self::$common_transformation_str, + '', + array(), + $updated_attributes + ); + $this->assertEquals($expected_overriden_attributes_tag, $tag_with_custom_overriden_attribute); + } + + public function test_dpr_auto() + { + // should support width=auto + $tag = cl_image_tag("hello", array("dpr" => "auto", "format" => "png")); + $this->assertEquals( + "", + $tag + ); + } + + public function test_cl_sprite_tag() + { + $url = cl_sprite_tag("mytag", array("crop" => "fill", "width" => 10, "height" => 10)); + $this->assertEquals( + "", + $url + ); + } + + public function test_cl_video_thumbnail_path() + { + $this->assertEquals(cl_video_thumbnail_path('movie_id'), TagTest::VIDEO_UPLOAD_PATH . "movie_id.jpg"); + $this->assertEquals( + cl_video_thumbnail_path('movie_id', array('width' => 100)), + TagTest::VIDEO_UPLOAD_PATH . "w_100/movie_id.jpg" + ); + } + + public function test_cl_video_thumbnail_tag() + { + $expected_url = TagTest::VIDEO_UPLOAD_PATH . "movie_id.jpg"; + $this->assertEquals( + cl_video_thumbnail_tag('movie_id'), + "" + ); + + $expected_url = TagTest::VIDEO_UPLOAD_PATH . "w_100/movie_id.jpg"; + $this->assertEquals( + cl_video_thumbnail_tag('movie_id', array('width' => 100)), + "" + ); + } + + public function test_cl_video_tag() + { + //default + $expected_url = TagTest::VIDEO_UPLOAD_PATH . "movie"; + $this->assertEquals( + cl_video_tag('movie'), + "" + ); + } + + public function test_cl_video_tag_with_attributes() + { + //test video attributes + $expected_url = TagTest::VIDEO_UPLOAD_PATH . "movie"; + $this->assertEquals( + cl_video_tag( + 'movie', + array('autoplay' => true, 'controls', 'loop', 'muted' => "true", 'preload', 'style' => 'border: 1px') + ), + "" + ); + } + + public function test_cl_video_tag_with_transformation() + { + //test video attributes + $options = array( + 'source_types' => "mp4", + 'html_height' => "100", + 'html_width' => "200", + 'video_codec' => array('codec' => 'h264'), + 'audio_codec' => 'acc', + 'start_offset' => 3, + ); + $expected_url = TagTest::VIDEO_UPLOAD_PATH . "ac_acc,so_3,vc_h264/movie"; + $this->assertEquals( + cl_video_tag('movie', $options), + "" + ); + + unset($options['source_types']); + $this->assertEquals( + cl_video_tag('movie', $options), + "" + ); + + unset($options['html_height']); + unset($options['html_width']); + $options['width'] = 250; + $expected_url = TagTest::VIDEO_UPLOAD_PATH . "ac_acc,so_3,vc_h264,w_250/movie"; + $this->assertEquals( + cl_video_tag('movie', $options), + "" + ); + + $expected_url = TagTest::VIDEO_UPLOAD_PATH . "ac_acc,c_fit,so_3,vc_h264,w_250/movie"; + $options['crop'] = 'fit'; + $this->assertEquals( + cl_video_tag('movie', $options), + "" + ); + } + + public function test_cl_video_tag_with_fallback() + { + $expected_url = TagTest::VIDEO_UPLOAD_PATH . "movie"; + $fallback = "Cannot display video"; + $this->assertEquals( + cl_video_tag('movie', array('fallback_content' => $fallback)), + "" + ); + $this->assertEquals( + cl_video_tag('movie', array('fallback_content' => $fallback, 'source_types' => "mp4")), + "" + ); + } + + public function test_cl_video_tag_with_source_types() + { + $expected_url = TagTest::VIDEO_UPLOAD_PATH . "movie"; + $this->assertEquals( + cl_video_tag('movie', array('source_types' => array('ogv', 'mp4'))), + "" + ); + } + + public function test_cl_video_tag_with_source_transformation() + { + $expected_url = TagTest::VIDEO_UPLOAD_PATH . "q_50/w_100/movie"; + $expected_ogv_url = TagTest::VIDEO_UPLOAD_PATH . "q_50/q_70,w_100/movie"; + $expected_mp4_url = TagTest::VIDEO_UPLOAD_PATH . "q_50/q_30,w_100/movie"; + $this->assertEquals( + cl_video_tag( + 'movie', + array( + 'width' => 100, + 'transformation' => array(array('quality' => 50)), + 'source_transformation' => array( + 'ogv' => array('quality' => 70), + 'mp4' => array('quality' => 30), + ), + ) + ), + "" + ); + + $this->assertEquals( + cl_video_tag( + 'movie', + array( + 'width' => 100, + 'transformation' => array(array('quality' => 50)), + 'source_transformation' => array( + 'ogv' => array('quality' => 70), + 'mp4' => array('quality' => 30), + ), + 'source_types' => array('webm', 'mp4'), + ) + ), + "" + ); + } + + public function test_cl_video_tag_with_poster() + { + $expected_url = TagTest::VIDEO_UPLOAD_PATH . "movie"; + + $expected_poster_url = 'http://image/somewhere.jpg'; + $this->assertEquals( + cl_video_tag('movie', array('poster' => $expected_poster_url, 'source_types' => "mp4")), + "" + ); + + $expected_poster_url = TagTest::VIDEO_UPLOAD_PATH . "g_north/movie.jpg"; + $this->assertEquals( + cl_video_tag( + 'movie', + array('poster' => array('gravity' => 'north'), 'source_types' => "mp4") + ), + "" + ); + + $expected_poster_url = TagTest::DEFAULT_UPLOAD_PATH . "g_north/my_poster.jpg"; + $this->assertEquals( + cl_video_tag( + 'movie', + array( + 'poster' => array('gravity' => 'north', 'public_id' => 'my_poster', 'format' => 'jpg'), + 'source_types' => "mp4", + ) + ), + "" + ); + + $this->assertEquals( + cl_video_tag('movie', array('poster' => null, 'source_types' => "mp4")), + "" + ); + + $this->assertEquals( + cl_video_tag('movie', array('poster' => false, 'source_types' => "mp4")), + "" + ); + } + + /** + * Check that cl_video_tag encodes special characters. + */ + public function test_cl_video_tag_special_characters_encoding() + { + $expected_url = TagTest::VIDEO_UPLOAD_PATH . "movie%27s%20id%21%40%23%24%25%5E%26%2A%28"; + + $this->assertEquals( + "", + cl_video_tag("movie's id!@#$%^&*(", array('source_types' => "mp4")) + ); + } + + public function test_cl_video_tag_default_sources() + { + $expected_url = self::VIDEO_UPLOAD_PATH . "%smovie.%s"; + + $this->assertEquals( + "", + cl_video_tag('movie', array('sources' => default_video_sources())) + ); + } + + public function test_cl_video_tag_custom_sources() + { + $custom_sources = [ + [ + "type" => "mp4", + "codecs" => "vp8, vorbis", + "transformations" => ["video_codec" => "auto"] + ], + [ + "type" => "webm", + "codecs" => "avc1.4D401E, mp4a.40.2", + "transformations" => ["video_codec" => "auto"] + ] + ]; + $expected_url = self::VIDEO_UPLOAD_PATH . "%smovie.%s"; + + $this->assertEquals( + "", + cl_video_tag('movie', array('sources' => $custom_sources)) + ); + } + + public function test_cl_video_tag_sources_codecs_array() + { + $custom_sources = [ + [ + "type" => "mp4", + "codecs" => ["vp8", "vorbis"], + "transformations" => ["video_codec" => "auto"] + ], + [ + "type" => "webm", + "codecs" => ["avc1.4D401E", "mp4a.40.2"], + "transformations" => ["video_codec" => "auto"] + ] + ]; + $expected_url = self::VIDEO_UPLOAD_PATH . "%smovie.%s"; + + $this->assertEquals( + "", + cl_video_tag('movie', array('sources' => $custom_sources)) + ); + } + + public function test_cl_video_tag_sources_with_transformation() + { + $options = array( + 'source_types' => "mp4", + 'html_height' => "100", + 'html_width' => "200", + 'video_codec' => array('codec' => 'h264'), + 'audio_codec' => 'acc', + 'start_offset' => 3, + 'sources' => default_video_sources() + ); + $expected_url = self::VIDEO_UPLOAD_PATH . "ac_acc,so_3,%smovie.%s"; + + $this->assertEquals( + "", + cl_video_tag('movie', $options) + ); + } + + public function test_upload_tag() + { + $pattern = "//"; + $this->assertRegExp($pattern, cl_upload_tag('image')); + $this->assertRegExp($pattern, cl_image_upload_tag('image')); + + $pattern = "//"; + $this->assertRegExp( + $pattern, + cl_upload_tag('image', array('chunk_size' => 5000000)) + ); + + $pattern = "//"; + $this->assertRegExp( + $pattern, + cl_upload_tag('image', array("html" => array('class' => 'classy'))) + ); + } + + public function test_cl_source_tag() + { + $expected_tag = self::get_expected_cl_source_tag( + self::$public_id, + self::$common_transformation_str, + '', + self::$breakpoints_arr, + array('sizes' => self::$sizes_attr, "media" => generate_media_attr(["max_width" =>self::$max_width])) + ); + + $source_tag_with_srcset = cl_source_tag( + self::$public_id, + array_merge( + self::$common_image_options, + array( + 'srcset' => array_merge( + self::$common_srcset, + array('sizes' => true) + ) + ), + array( + 'media' => array("max_width" =>self::$max_width) + ) + ) + ); + + $this->assertEquals( + $expected_tag, + $source_tag_with_srcset, + 'Should create source tag with srcset and sizes attributes with provided breakpoints' + ); + } + + public function test_cl_picture_tag() + { + $tag = cl_picture_tag( + self::$public_id, + self::$fill_transformation, + [ + [ + "max_width" =>self::$min_width, + "transformation" => ["effect" => "sepia", "angle" => 17, "width" => self::$min_width] + ], + [ + "min_width" => self::$min_width, + "max_width" => self::$max_width, + "transformation" => ["effect" => "colorize", "angle" => 18, "width" => self::$max_width] + + ], + [ + "min_width" => self::$max_width, + "transformation" => ["effect" => "blur", "angle" => 19, "width" => self::$max_width] + ] + ] + ); + + + $expected_source1 = self::get_expected_cl_source_tag( + self::$public_id, + self::$fill_trans_str . "/" . "a_17,e_sepia,w_" . self::$min_width, + '', + [], + ["media" => generate_media_attr(["max_width" =>self::$min_width])] + ); + + $expected_source2 = self::get_expected_cl_source_tag( + self::$public_id, + self::$fill_trans_str . "/" . "a_18,e_colorize,w_" . self::$max_width, + '', + [], + ["media" => generate_media_attr(["min_width" => self::$min_width, "max_width" =>self::$max_width])] + + ); + + $expected_source3 = self::get_expected_cl_source_tag( + self::$public_id, + self::$fill_trans_str . "/" . "a_19,e_blur,w_" . self::$max_width, + '', + [], + ["media" => generate_media_attr(["min_width" => self::$max_width])] + ); + + $expected_img = self::get_expected_cl_image_tag( + self::$public_id, + self::$fill_trans_str, + '', + [], + ['height' => self::$max_width, 'width' => self::$max_width] + ); + + $exp = "" . $expected_source1 . $expected_source2 . $expected_source3 . $expected_img . ""; + + $this->assertEquals($exp, $tag); + } + + private function cloudinary_url_assertion($source, $options, $expected, $expected_options = array()) + { + $url = Cloudinary::cloudinary_url($source, $options); + $this->assertEquals($expected_options, $options); + $this->assertEquals($expected, $url); + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/TestHelper.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/TestHelper.php new file mode 100644 index 0000000..371ba01 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/TestHelper.php @@ -0,0 +1,270 @@ +parameters = array(); + $this->apiResponse = <<apiResponse = str_replace("\n", "\r\n", $this->apiResponse); + + $this->uploadResponse = '{"public_id":"oej8n7ezhwmk1fp1xqfd"}'; + } + + public static function mockApi($test) + { + self::mockRequest($test, Curl::$instance->apiResponse); + } + + public static function mockUpload($test) + { + self::mockRequest($test, Curl::$instance->uploadResponse); + } + + /** + * @param \PHPUnit\Framework\TestCase $test Test case to mock + * @param $mocked_response + */ + public static function mockRequest($test, $mocked_response) + { + Curl::$instance = $test + ->getMockBuilder("\\Cloudinary\\Curl") + ->setMethods(array("exec", "getinfo")) + ->getMock(); + + Curl::$instance + ->method("exec") + ->will($test->returnValue($mocked_response)); + + Curl::$instance + ->method("getinfo") + ->will($test->returnValue(200)); + } + + public function exec($ch) + { + $this->result = \curl_exec($ch); + return $this->result; + } + + public function setopt($ch, $option, $value) + { + $this->parameters[$option] = $value; + return $this->globalSetopt($ch, $option, $value); + } + + public function globalSetopt($ch, $option, $value) + { + return \curl_setopt($ch, $option, $value); + } + + /** + * When stubbing exec() this function must be stubbed too to return code + * + * @inheritdoc + */ + public function getinfo($ch, $opt) + { + return \curl_getinfo($ch, $opt); + } + + public function init($url = null) + { + $this->url = $url; + return \curl_init($url); + } + + /** + * Returns the option that was set in the curl object + * @param string $option the name of the option + * @return mixed the value of the option + */ + public function getopt($option) + { + return $this->parameters[$option]; + } + + public function http_method() + { + if (array_key_exists(CURLOPT_CUSTOMREQUEST, $this->parameters)) { + return Curl::$instance->getopt(CURLOPT_CUSTOMREQUEST); + } else { + $method_array = array_keys( + array_intersect_key( + $this->parameters, + array("CURLOPT_POST" => 0, "CURLOPT_PUT" => 0, "CURLOPT_HTTPGET" => 0) + ) + ); + if (count($method_array) > 0) { + return preg_replace("/CURLOPT_(HTTP)?/", "", $method_array[0]); + } else { + return "POST"; + } + } + } + + public function url_path() + { + return parse_url($this->url, PHP_URL_PATH); + } + + /** + * Returns the POST fields that were meant to be sent to the server + * @return array an array of field name => value + */ + public function fields() + { + if ($this->http_method() == "GET") { + parse_str(parse_url($this->url, PHP_URL_QUERY), $params); + return $params; + } else { + return $this->parameters[CURLOPT_POSTFIELDS]; + } + } + } + + Curl::$instance = new Curl(); + + // Override global curl functions + + function curl_init($url = null) + { + return Curl::$instance->init($url); + } + + function curl_exec($ch) + { + $result = Curl::$instance->exec($ch); + return $result; + } + + function curl_setopt($ch, $option, $value) + { + return Curl::$instance->setopt($ch, $option, $value); + } + + function curl_getinfo($ch, $opt) + { + return Curl::$instance->getinfo($ch, $opt); + } + + + /** + * @param $test + * @param $name + * @param $expectedValue + * @param string $message + */ + function assertParam($test, $name, $expectedValue = null, $message = '') + { + $fields = Curl::$instance->fields(); + if (strlen($message) == 0) { + $message = "should support the '$name' parameter"; + } + $test->assertArrayHasKey($name, $fields, $message); + if ($expectedValue != null) { + $test->assertEquals($expectedValue, $fields[$name]); + } + } + + function assertJson($test, $actualValue, $expectedValue = null, $message = '') + { + if (strlen($message) == 0) { + $message = "should coorectly encode JSON parameters"; + } + $test->assertJsonStringEqualsJsonString($actualValue, $expectedValue, $message); + } + + function assertNoParam($test, $name, $message = '') + { + $fields = Curl::$instance->fields(); + $test->assertArrayNotHasKey($name, $fields, $message); + } + + function assertPost($test, $message = "http method should be POST") + { + $test->assertEquals("POST", Curl::$instance->http_method(), $message); + } + + function assertPut($test, $message = "http method should be PUT") + { + $test->assertEquals("PUT", Curl::$instance->http_method(), $message); + } + + function assertGet($test, $message = "http method should be GET") + { + $test->assertEquals("GET", Curl::$instance->http_method(), $message); + } + + function assertDelete($test, $message = "http method should be DELETE") + { + $test->assertEquals("DELETE", Curl::$instance->http_method(), $message); + } + + function assertUrl($test, $path, $message = '') + { + $cloud_name = \Cloudinary::config_get("cloud_name"); + $test->assertEquals("/v1_1/" . $cloud_name . $path, Curl::$instance->url_path(), $message); + } + + function assertHasHeader($test, $header, $message = '') + { + $headers = Curl::$instance->getopt(CURLOPT_HTTPHEADER); + $test->assertTrue(is_array($headers), $message); + $names = array(); + foreach ($headers as $h) { + $chunks = explode(":", $h); + // header names are case-insensitive according to rfc7230 + $names[] = strtolower(trim($chunks[0])); + } + $test->assertContains(strtolower($header), $names, $message); + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/UploaderTest.php b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/UploaderTest.php new file mode 100644 index 0000000..704c526 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/UploaderTest.php @@ -0,0 +1,860 @@ + 45, "crop" => "scale"]; + protected static $rbp_format = "png"; + protected static $rbp_values = [206, 50]; + protected static $rbp_params; + + public static function setUpBeforeClass() + { + Curl::$instance = new Curl(); + + self::$rbp_params = [ + "use_cache" => true, + "responsive_breakpoints" => [ + [ + "create_derived" => false, + "transformation" => [ + "angle" => 90 + ], + "format" => 'gif' + ], + [ + "create_derived" => false, + "transformation" => self::$rbp_trans, + "format" => self::$rbp_format + ], + [ + "create_derived" => false + ] + ], + "type" => "upload", + "tags" => array(TEST_TAG, UNIQUE_TEST_TAG), + ]; + } + + public function setUp() + { + \Cloudinary::reset_config(); + if (!Cloudinary::config_get("api_secret")) { + $this->markTestSkipped('Please setup environment for Upload test to run'); + } + $this->url_prefix = Cloudinary::config_get("upload_prefix", "https://api.cloudinary.com"); + } + + public function tearDown() + { + Curl::$instance = new Curl(); + } + + public static function tearDownAfterClass() + { + if (!Cloudinary::config_get("api_secret")) { + self::fail("You need to configure the cloudinary api for the tests to work."); + } + + $api = new Cloudinary\Api(); + + self::delete_resources($api); + } + + /** + * Delete all test related resources + * + * @param \Cloudinary\Api $api an initialized api object + */ + protected static function delete_resources($api) + { + try { + $api->delete_resources_by_tag(UNIQUE_TEST_TAG); + $api->delete_resources_by_tag(UNIQUE_TEST_SPRITE_TAG); + } catch (Exception $e) { + } + } + + public function test_upload() + { + $result = Uploader::upload(TEST_IMG, ["tags" => array(TEST_TAG, UNIQUE_TEST_TAG)]); + $this->assertEquals($result["width"], 241); + $this->assertEquals($result["height"], 51); + $expected_signature = Cloudinary::api_sign_request( + array( + "public_id" => $result["public_id"], + "version" => $result["version"], + ), + Cloudinary::config_get("api_secret") + ); + $this->assertEquals($result["signature"], $expected_signature); + Curl::mockUpload($this); + + Uploader::upload(TEST_IMG, array("ocr" => "adv_ocr", "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + $fields = Curl::$instance->fields(); + $this->assertArraySubset(array("ocr" => "adv_ocr"), $fields); + + // Test upload with metadata + Uploader::upload( + TEST_IMG, + array("metadata" => array("metadata_color" => "red", "metadata_shape" => "dodecahedron")) + ); + $fields = Curl::$instance->fields(); + $this->assertArraySubset(array("metadata" => "metadata_color=red|metadata_shape=dodecahedron"), $fields); + } + + public function test_upload_responsive_breakpoints_cache() + { + $cache = ResponsiveBreakpointsCache::instance(); + $cache->setCacheAdapter(new KeyValueCacheAdapter(new DummyCacheStorage())); + + $result = Uploader::upload(\Cloudinary\TEST_IMG, self::$rbp_params); + + $res = $cache->get( + $result["public_id"], + ["transformation" => self::$rbp_trans, "format" => self::$rbp_format] + ); + + $this::assertEquals(self::$rbp_values, $res); + } + + /* + * Should use filename passed in options as original_filename and not the actual filesystem file name + */ + public function test_upload_custom_filename() + { + $custom_filename = UNIQUE_TEST_ID . '_' . basename(TEST_IMG); + $result = Uploader::upload( + TEST_IMG, + [ + 'filename' => $custom_filename, + 'tags' => array(TEST_TAG, UNIQUE_TEST_TAG) + ] + ); + + // Note that original_filename strips extension + $this->assertEquals(pathinfo($custom_filename, PATHINFO_FILENAME), $result["original_filename"]); + } + + public function test_rename() + { + Curl::mockUpload($this); + Uploader::rename("foobar", "foobar2", array("overwrite" => true)); + assertUrl($this, "/image/rename"); + assertParam($this, "overwrite", 1); + assertParam($this, "from_public_id", "foobar"); + assertParam($this, "to_public_id", "foobar2"); + } + + public function test_rename_to_type() + { + Curl::mockUpload($this); + Uploader::rename("foobar", "foobar", array("to_type" => "private")); + assertUrl($this, "/image/rename"); + assertParam($this, "to_type", "private"); + assertParam($this, "from_public_id", "foobar"); + assertParam($this, "to_public_id", "foobar"); + } + + public function test_explicit() + { + Curl::mockUpload($this); + + Uploader::explicit( + "cloudinary", + array("type" => "twitter_name", "eager" => array("crop" => "scale", "width" => "2.0")) + ); + $fields = Curl::$instance->fields(); + $this->assertArraySubset(array("type" => "twitter_name", "eager" => "c_scale,w_2.0"), $fields); + Uploader::explicit("cloudinary", array("ocr" => "adv_ocr")); + $fields = Curl::$instance->fields(); + $this->assertArraySubset(array("ocr" => "adv_ocr"), $fields); + + // Test explicit with metadata + Uploader::explicit( + "cloudinary", + array("metadata" => array("metadata_color" => "red", "metadata_shape" => "dodecahedron")) + ); + $fields = Curl::$instance->fields(); + $this->assertArraySubset(array("metadata" => "metadata_color=red|metadata_shape=dodecahedron"), $fields); + } + + public function test_explicit_responsive_breakpoints_cache() + { + $cache = ResponsiveBreakpointsCache::instance(); + $cache->setCacheAdapter(new KeyValueCacheAdapter(new DummyCacheStorage())); + + $upload_result = Uploader::upload(\Cloudinary\TEST_IMG, ["tags" => array(TEST_TAG, UNIQUE_TEST_TAG),]); + + $result = Uploader::explicit($upload_result["public_id"], self::$rbp_params); + + $res = $cache->get( + $result["public_id"], + ["transformation" => self::$rbp_trans, "format" => self::$rbp_format] + ); + + $this::assertEquals(self::$rbp_values, $res); + } + + public function test_update_metadata() + { + Curl::mockUpload($this); + + Uploader::update_metadata( + array("metadata_color" => "red", "metadata_shape" => ""), + array("test_id_1", "test_id_2") + ); + $fields = Curl::$instance->fields(); + $this->assertArraySubset( + array("metadata" => "metadata_color=red|metadata_shape=", + "public_ids[0]" => "test_id_1", + "public_ids[1]" => "test_id_2", + ), + $fields + ); + } + + public function test_build_eager() + { + $eager = array( + "0" => array( + "0" => array("width" => 3204, "crop" => "scale"), + ), + "1" => array( + "angle" => array("0" => 127), + "format" => "jpg", + ), + ); + $this->assertEquals("c_scale,w_3204|a_127/jpg", Cloudinary::build_eager($eager)); + } + + public function test_eager() + { + Curl::mockUpload($this); + Uploader::upload(TEST_IMG, array("eager" => array("crop" => "scale", "width" => "2.0"), "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + $fields = Curl::$instance->fields(); + $this->assertArraySubset(array("eager" => "c_scale,w_2.0"), $fields); + } + + public function test_upload_async() + { + Curl::mockUpload($this); + Uploader::upload( + TEST_IMG, + array( + "transformation" => array("crop" => "scale", "width" => "2.0"), + "async" => true, + "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),) + ); + $fields = Curl::$instance->fields(); + $this->assertArraySubset(array("async" => true), $fields); + } + + public function test_quality_override() + { + Curl::mockUpload($this); + $values = ['auto:advanced', 'auto:best', '80:420', 'none']; + foreach ($values as $value) { + Uploader::upload(TEST_IMG, [ + "quality_override" => $value, + "tags" => array(TEST_TAG, UNIQUE_TEST_TAG), + ]); + assertParam($this, "quality_override", $value); + Uploader::explicit( + "api_test", + array("quality_override" => $value, "type" => "upload") + ); + assertParam($this, "quality_override", $value); + } + } + + public function test_headers() + { + Curl::mockUpload($this); + Uploader::upload(TEST_IMG, array("headers" => array("Link: 1"), "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + assertParam($this, "headers", "Link: 1"); + Uploader::upload(TEST_IMG, array("headers" => array("Link" => "1"), "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + assertParam($this, "headers", "Link: 1"); + } + + public function test_text() + { + $result = Uploader::text("hello world"); + $this->assertGreaterThan(1, $result["width"]); + $this->assertGreaterThan(1, $result["height"]); + } + + public function test_tags() + { + $api = new \Cloudinary\Api(); + $result = Uploader::upload(TEST_IMG, ["tags" => array(TEST_TAG, UNIQUE_TEST_TAG),]); + Curl::mockUpload($this); + Uploader::add_tag("tag1", "foobar"); + assertUrl($this, "/image/tags"); + assertPost($this); + assertParam($this, "public_ids[0]", "foobar"); + assertParam($this, "command", "add"); + assertParam($this, "tag", "tag1"); + + Uploader::remove_tag("tag1", "foobar"); + assertUrl($this, "/image/tags"); + assertPost($this); + assertParam($this, "public_ids[0]", "foobar"); + assertParam($this, "command", "remove"); + assertParam($this, "tag", "tag1"); + + Uploader::replace_tag("tag3", "foobar"); + assertUrl($this, "/image/tags"); + assertPost($this); + assertParam($this, "public_ids[0]", "foobar"); + assertParam($this, "command", "replace"); + assertParam($this, "tag", "tag3"); + } + + /** + * Should successfully remove all tags for specified public IDs + */ + public function test_remove_all_tags() + { + Curl::mockUpload($this); + $public_id = UNIQUE_TEST_ID; + Uploader::remove_all_tags($public_id); + assertPost($this); + assertUrl($this, '/image/tags'); + assertParam($this, "command", "remove_all"); + } + + + /** + * Test issue #33 - HTTP 502 when providing a large public ID list + */ + public function test_huge_public_id_list() + { + $ids = array(); + for ($i = 1; $i < 200; $i++) { + $ids[] = "foobarfoobarfoobarfoobarfoobar"; + } + Uploader::add_tag("huge_list", $ids); + assertParam($this, "public_ids[0]", $ids[0]); + } + + public function test_use_filename() + { + $result = Uploader::upload(TEST_IMG, array("use_filename" => true, "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + $this->assertRegExp('/logo_[a-zA-Z0-9]{6}/', $result["public_id"]); + $result = Uploader::upload(TEST_IMG, array("use_filename" => true, "unique_filename" => false, "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + $this->assertEquals("logo", $result["public_id"]); + } + + public function test_allowed_formats() + { + //should allow whitelisted formats if allowed_formats + $formats = array("png"); + $result = Uploader::upload(TEST_IMG, array("allowed_formats" => $formats, "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + $this->assertEquals($result["format"], "png"); + } + + public function test_allowed_formats_with_illegal_format() + { + //should prevent non whitelisted formats from being uploaded if allowed_formats is specified + $error_found = false; + $formats = array("jpg"); + try { + Uploader::upload(TEST_IMG, array("allowed_formats" => $formats, "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + } catch (Exception $e) { + $error_found = true; + } + $this->assertTrue($error_found); + } + + public function test_allowed_formats_with_format() + { + //should allow non whitelisted formats if type is specified and convert to that type + $formats = array("jpg"); + $result = Uploader::upload(TEST_IMG, array("allowed_formats" => $formats, "format" => "jpg", "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + $this->assertEquals("jpg", $result["format"]); + } + + public function test_face_coordinates() + { + //should allow sending face and custom coordinates + $face_coordinates = array(array(120, 30, 109, 51), array(121, 31, 110, 51)); + $result = Uploader::upload(TEST_IMG, array("face_coordinates" => $face_coordinates, "faces" => true, "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + $this->assertEquals($face_coordinates, $result["faces"]); + + $different_face_coordinates = array(array(122, 32, 111, 152)); + $custom_coordinates = array(1, 2, 3, 4); + Uploader::explicit( + $result["public_id"], + array( + "face_coordinates" => $different_face_coordinates, + "custom_coordinates" => $custom_coordinates, + "faces" => true, + "type" => "upload", + ) + ); + $api = new \Cloudinary\Api(); + $info = $api->resource($result["public_id"], array("faces" => true, "coordinates" => true)); + $this->assertEquals($info["faces"], $different_face_coordinates); + $this->assertEquals( + $info["coordinates"], + array("faces" => $different_face_coordinates, "custom" => array($custom_coordinates)) + ); + } + + public function test_quality_analysis() + { + //should return quality analysis information + $result = Uploader::upload(TEST_IMG, ["quality_analysis" => true, "tags" => [TEST_TAG, UNIQUE_TEST_TAG]]); + + $this->assertArrayHasKey("quality_analysis", $result); + $this->assertInternalType("double", $result["quality_analysis"]["focus"]); + + $explicitRes = Uploader::explicit($result["public_id"], ["quality_analysis" => true, "type" => "upload"]); + + $this->assertArrayHasKey("quality_analysis", $explicitRes); + $this->assertInternalType("double", $explicitRes["quality_analysis"]["focus"]); + } + + public function test_cinemagraph_analysis() + { + //Should allow cinemagraph_analysis parameter + + Curl::mockUpload($this); + + Uploader::upload(TEST_IMG, ["cinemagraph_analysis" => true]); + + assertParam($this, "cinemagraph_analysis", "1"); + + Uploader::explicit("Cloudinary", ["cinemagraph_analysis" => true, "type" => "upload"]); + + assertParam($this, "cinemagraph_analysis", "1"); + } + + public function test_context() + { + //should allow sending context + $context = array("caption" => "cap=caps", "alt" => "alternative|alt=a"); + $result = Uploader::upload(TEST_IMG, array("context" => $context, "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + + $api = new \Cloudinary\Api(); + $info = $api->resource($result["public_id"]); + $this->assertEquals($context, $info["context"]["custom"]); + + $fields = Curl::$instance->parameters[CURLOPT_POSTFIELDS]; + $this->assertEquals('caption=cap\=caps|alt=alternative\|alt\=a', $fields['context']); + } + + public function test_context_api() + { + $api = new \Cloudinary\Api(); + $result = Uploader::upload(TEST_IMG, ["tags" => array(TEST_TAG, UNIQUE_TEST_TAG),]); + Uploader::add_context('alt=testAlt|custom=testCustom', $result['public_id']); + assertUrl($this, "/image/context"); + assertPost($this); + assertParam($this, "public_ids[0]", $result['public_id']); + assertParam($this, "command", "add"); + assertParam($this, "context", "alt=testAlt|custom=testCustom"); + + $info = $api->resource($result["public_id"]); + $this->assertEquals( + array("custom" => array("alt" => "testAlt", "custom" => "testCustom")), + $info["context"] + ); + + Uploader::remove_all_context($result['public_id']); + assertUrl($this, "/image/context"); + assertGet($this); + + $info = $api->resource($result["public_id"]); + $this->assertEquals(false, isset($info["context"])); + } + + public function test_cl_form_tag() + { + Cloudinary::config( + array( + "cloud_name" => "test123", + "secure_distribution" => null, + "private_cdn" => false, + "api_key" => "1234", + ) + ); + + $form = cl_form_tag( + "http://callback.com", + array("public_id" => "hello", "form" => array("class" => "uploader")) + ); + $this->assertRegExp( + << + + + + +<\/form># +TAG + , + $form + ); + } + + public function test_cl_image_upload_tag() + { + Cloudinary::config( + array( + "cloud_name" => "test123", + "secure_distribution" => null, + "private_cdn" => false, + "api_key" => "1234", + ) + ); + + $tag = cl_image_upload_tag("image", array("public_id" => "hello", "html" => array("class" => "uploader"))); + + $pattern = "##"; + + $this->assertRegExp($pattern, $tag); + } + + public function test_manual_moderation() + { + // should support setting manual moderation status + $resource = Uploader::upload(TEST_IMG, array("moderation" => "manual", "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + $this->assertEquals($resource["moderation"][0]["status"], "pending"); + $this->assertEquals($resource["moderation"][0]["kind"], "manual"); + } + + /** + * @expectedException \Cloudinary\Error + * @expectedExceptionMessage Raw convert is invalid + */ + public function test_raw_conversion() + { + // should support requesting raw_convert + Uploader::upload("tests/docx.docx", array("resource_type" => "raw", "raw_convert" => "illegal", "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + } + + /** + * @expectedException \Cloudinary\Error + * @expectedExceptionMessage is not valid + */ + public function test_categorization() + { + // should support requesting categorization + Uploader::upload(TEST_IMG, array("categorization" => "illegal", "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + } + + /** + * @expectedException \Cloudinary\Error + * @expectedExceptionMessage Detection is invalid + */ + public function test_detection() + { + // should support requesting detection + Uploader::upload(TEST_IMG, array("detection" => "illegal", "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + } + + /** + * @expectedException \Cloudinary\Error + * @expectedExceptionMessage Background removal is invalid + */ + public function test_background_removal() + { + // should support requesting background_removal + Uploader::upload(TEST_IMG, array("background_removal" => "illegal", "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + } + + public function test_large_upload() + { + $filename = UNIQUE_TEST_ID . '_cld_upload_large'; + $temp_file_name = tempnam(sys_get_temp_dir(), $filename . ".") . ".bmp"; + $temp_file = fopen($temp_file_name, 'w'); + fwrite( + $temp_file, + "BMJ\xB9Y\x00\x00\x00\x00\x00\x8A\x00\x00\x00|\x00\x00\x00x\x05\x00\x00x\x05\x00\x00\x01\x00\x18\x00" . + "\x00\x00\x00\x00\xC0\xB8Y\x00a\x0F\x00\x00a\x0F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF" . + "\x00\x00\xFF\x00\x00\xFF\x00\x00\x00\x00\x00\x00\xFFBGRs\x00\x00\x00\x00\x00\x00\x00\x00T\xB8\x1E" . + "\xFC\x00\x00\x00\x00\x00\x00\x00\x00fff\xFC\x00\x00\x00\x00\x00\x00\x00\x00\xC4\xF5(\xFF\x00\x00\x00" . + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ); + for ($i = 1; $i <= 588000; $i++) { + fwrite($temp_file, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"); + } + fclose($temp_file); + $this->assertEquals(5880138, filesize($temp_file_name)); + + $resource = Uploader::upload_large( + $temp_file_name, + array( + "chunk_size" => 5243000, + "tags" => array("upload_large_tag", TEST_TAG, UNIQUE_TEST_TAG), + "allowed_formats" => ["bmp"] + ) + ); + $this->assertEquals($resource["tags"], array("upload_large_tag", TEST_TAG, UNIQUE_TEST_TAG)); + $this->assertEquals($resource["resource_type"], "raw"); + $this->assertEquals($resource["original_filename"], pathinfo($temp_file_name, PATHINFO_FILENAME)); + + assertHasHeader($this, 'X-Unique-Upload-Id'); + + $resource = Uploader::upload_large( + $temp_file_name, + array("chunk_size" => 5243000, + "tags" => array("upload_large_tag", TEST_TAG, UNIQUE_TEST_TAG), + "resource_type" => "image", + "use_filename" => true, + "unique_filename" => false, + "filename" => $filename + ) + ); + + $this->assertEquals($resource["tags"], array("upload_large_tag", TEST_TAG, UNIQUE_TEST_TAG)); + $this->assertEquals($resource["resource_type"], "image"); + $this->assertEquals($filename, $resource["original_filename"]); + $this->assertEquals($resource["original_filename"], $resource["public_id"]); + $this->assertEquals($resource["width"], 1400); + $this->assertEquals($resource["height"], 1400); + + assertHasHeader($this, 'X-Unique-Upload-Id'); + + #where chunk size equals file size + $resource = Uploader::upload_large( + $temp_file_name, + array("chunk_size" => 5880138, + "tags" => array("upload_large_tag", TEST_TAG, UNIQUE_TEST_TAG), "resource_type" => "image") + ); + + $this->assertEquals($resource["tags"], array("upload_large_tag", TEST_TAG, UNIQUE_TEST_TAG)); + $this->assertEquals($resource["resource_type"], "image"); + $this->assertEquals($resource["width"], 1400); + $this->assertEquals($resource["height"], 1400); + + assertHasHeader($this, 'X-Unique-Upload-Id'); + } + + public function test_upload_large_url() + { + $file = "http://cloudinary.com/images/old_logo.png"; + Curl::mockUpload($this); + Uploader::upload_large($file, ["tags" => array(TEST_TAG, UNIQUE_TEST_TAG),]); + // we can't mock "upload" method due to static modifier, + // so we check that file is passed as url + assertParam($this, "file", $file); + } + + public function test_upload_non_local_file() + { + $files = [ + "ftp://ftp.cloudinary.com/images/old_logo.png", + "http://cloudinary.com/images/old_logo.png", + "https://cloudinary.com/images/old_logo.png", + "s3://s3-us-west-2.amazonaws.com/cloudinary/images/old_logo.png", + "gs://cloudinary/images/old_logo.png", + "data:image/gif;charset=utf-8;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", + "data:image/gif;param1=value1;param2=value2;". + "base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", + "data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPg", + Cloudinary::BLANK + ]; + + Curl::mockUpload($this); + + foreach ($files as $file) { + Uploader::upload($file); + + assertParam($this, "file", $file); + } + } + + /** + * @expectedException Cloudinary\Error + * @expectedExceptionMessage does not exist + */ + public function test_upload_non_existing_file() + { + Uploader::upload(TEST_IMG . '_non_existing'); + } + + public function test_upload_preset() + { + // should support unsigned uploading using presets + Curl::mockUpload($this); + Uploader::unsigned_upload(TEST_IMG, TEST_PRESET_NAME); + assertUrl($this, "/image/upload"); + assertPost($this); + assertParam($this, "file"); + assertNoParam($this, "signature"); + assertParam($this, "upload_preset", TEST_PRESET_NAME); + } + + /** + * @expectedException Cloudinary\Error + * @expectedExceptionMessage timed out + */ + public function test_upload_timeout() + { + $timeout = Cloudinary::config_get("timeout"); + Cloudinary::config(array("timeout" => 1)); + $this->assertEquals(Cloudinary::config_get("timeout"), 1); + try { + // Use a lengthy PNG transformation + $transformation = array("crop" => "scale", "width" => "2.0", "angle" => 33); + Uploader::upload( + TEST_IMG, + array("eager" => array("transformation" => array($transformation, $transformation)), + "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),) + ); + } catch (Exception $e) { + // Finally not supported in PHP 5.3 + Cloudinary::config(array("timeout", $timeout)); + throw $e; + } + } + + public function test_upload_responsive_breakpoints() + { + $response = Uploader::upload( + TEST_IMG, + array("responsive_breakpoints" => array(array("create_derived" => false)), + "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),) + ); + $this->assertArrayHasKey( + "responsive_breakpoints", + $response, + "Should return responsive_breakpoints information" + ); + $this->assertEquals(2, count($response["responsive_breakpoints"][0]["breakpoints"])); + } + + /** + * Should allow the user to define ACL in the upload parameters + */ + public function test_access_control() + { + Curl::mockUpload($this); + + # Should accept an array of strings + $acl = array("access_type" => "anonymous", + "start" => "2018-02-22 16:20:57 +0200", + "end"=> "2018-03-22 00:00 +0200" + ); + $exp_acl = '[{"access_type":"anonymous",' . + '"start":"2018-02-22 16:20:57 +0200",' . + '"end":"2018-03-22 00:00 +0200"}]'; + + Uploader::upload(TEST_IMG, array("access_control" => $acl, "tags" => array(TEST_TAG, UNIQUE_TEST_TAG))); + + assertParam($this, "access_control", $exp_acl); + + # Should accept an array of datetime objects + $acl_2 = array("access_type" => "anonymous", + "start" => new \DateTime("2019-02-22 16:20:57Z"), + "end"=> new \DateTime("2019-03-22T00:00:00+02:00") + ); + $exp_acl_2 = '[{"access_type":"anonymous",' . + '"start":"2019-02-22T16:20:57+0000",' . + '"end":"2019-03-22T00:00:00+0200"}]'; + + Uploader::upload(TEST_IMG, array("access_control" => $acl_2, "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + + assertParam($this, "access_control", $exp_acl_2); + + # Should accept a JSON string + $acl_str = '{"access_type":"anonymous",' . + '"start":"2019-02-22 16:20:57 +0200",' . + '"end":"2019-03-22 00:00 +0200"}'; + + $exp_acl_str = '[{"access_type":"anonymous",' . + '"start":"2019-02-22 16:20:57 +0200",' . + '"end":"2019-03-22 00:00 +0200"}]'; + + Uploader::upload(TEST_IMG, array("access_control" => $acl_str, "tags" => array(TEST_TAG, UNIQUE_TEST_TAG),)); + + assertParam($this, "access_control", $exp_acl_str); + + # Should accept an array of all the above values + $array_of_acl = array($acl, $acl_2); + $exp_array_of_acl = '[' . implode( + ",", + array_map( + function ($v) { + return substr($v, 1, -1); + }, + array($exp_acl, $exp_acl_2) + ) + ) . ']'; + + Uploader::upload(TEST_IMG, array("access_control" => $array_of_acl, "tags" => array(TEST_TAG, UNIQUE_TEST_TAG))); + + assertParam($this, "access_control", $exp_array_of_acl); + + # Should throw InvalidArgumentException on invalid values + $invalid_values = array(array(array()), array("not_an_array"), array(7357)); + foreach ($invalid_values as $value) { + try { + Uploader::upload(TEST_IMG, array("access_control" => $value, "tags" => array(TEST_TAG, UNIQUE_TEST_TAG))); + $this->fail('InvalidArgumentException was not thrown'); + } catch (\InvalidArgumentException $e) { + } + } + } + + /** + * Should support `format` parameter in responsive breakpoints settings + */ + public function test_responsive_breakpoints_format() + { + $result = Uploader::upload(TEST_IMG, array( + "tags" => array(TEST_TAG, UNIQUE_TEST_TAG), + "responsive_breakpoints" => array( + "create_derived" => true, + "transformation" => array( + "angle" => 90 + ), + "format" => 'gif' + ) + )); + $this->assertNotNull($result["responsive_breakpoints"]); + $this->assertEquals($result["responsive_breakpoints"][0]["transformation"], "a_90"); + $this->assertRegExp('/\.gif$/', $result["responsive_breakpoints"][0]["breakpoints"][0]["url"]); + } + + /** + * Should generate a sprite from all images associated with a tag + */ + public function test_sprite_generation() + { + for ($i=0; $i<2; $i++) { + Uploader::upload(TEST_IMG, array("tags" => array(UNIQUE_TEST_SPRITE_TAG))); + } + $response = Uploader::generate_sprite(UNIQUE_TEST_SPRITE_TAG); + try { + Uploader::destroy($response['public_id'], array('type' => 'sprite')); + } catch (Exception $e) { + error_log("Failed to delete generated sprite: $e"); + } + + $this->assertEquals(2, count($response["image_infos"])); + } + } +} diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/docx.docx b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/docx.docx new file mode 100644 index 0000000..d179509 Binary files /dev/null and b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/docx.docx differ diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/favicon.ico b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/favicon.ico new file mode 100644 index 0000000..f1c9a23 Binary files /dev/null and b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/favicon.ico differ diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/logo.png b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/logo.png new file mode 100644 index 0000000..10d58c4 Binary files /dev/null and b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tests/logo.png differ diff --git a/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tools/update_version.sh b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tools/update_version.sh new file mode 100644 index 0000000..e6be39f --- /dev/null +++ b/lib/CloudinaryExtension/vendor/cloudinary/cloudinary_php/tools/update_version.sh @@ -0,0 +1,197 @@ +#!/usr/bin/env bash + +# Update version number and prepare for publishing the new version + +set -e + +# Empty to run the rest of the line and "echo" for a dry run +CMD_PREFIX= + +# Add a quote if this is a dry run +QUOTE= + +NEW_VERSION= + +function echo_err +{ + echo "$@" 1>&2; +} + +function usage +{ + echo "Usage: $0 [parameters]" + echo " -v | --version " + echo " -d | --dry-run print the commands without executing them" + echo " -h | --help print this information and exit" + echo + echo "For example: $0 -v 1.2.3" +} + +function process_arguments +{ + while [ "$1" != "" ]; do + case $1 in + -v | --version ) + shift + NEW_VERSION=${1:-} + if ! [[ "${NEW_VERSION}" =~ [0-9]+\.[0-9]+\.[0-9]+(\-.+)? ]]; then + echo_err "You must supply a new version after -v or --version" + echo_err "For example:" + echo_err " 1.2.3" + echo_err " 1.2.3-rc1" + echo_err "" + usage; return 1 + fi + ;; + -d | --dry-run ) + CMD_PREFIX=echo + echo "Dry Run" + echo "" + ;; + -h | --help ) + usage; return 0 + ;; + * ) + usage; return 1 + esac + shift || true + done +} + +# Intentionally make pushd silent +function pushd +{ + command pushd "$@" > /dev/null +} + +# Intentionally make popd silent +function popd +{ + command popd "$@" > /dev/null +} + +# Check if one version is less than or equal than other +# Example: +# ver_lte 1.2.3 1.2.3 && echo "yes" || echo "no" # yes +# ver_lte 1.2.3 1.2.4 && echo "yes" || echo "no" # yes +# ver_lte 1.2.4 1.2.3 && echo "yes" || echo "no" # no +function ver_lte +{ + [ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ] +} + +# Extract the last entry or entry for a given version +# The function is not currently used in this file. +# Examples: +# changelog_last_entry +# changelog_last_entry 1.10.0 +# +function changelog_last_entry +{ + sed -e "1,/^${1}/d" -e '/^=/d' -e '/^$/d' -e '/^[0-9]/,$d' CHANGELOG.md +} + +function verify_dependencies +{ + # Test if the gnu grep is installed + if ! grep --version | grep -q GNU + then + echo_err "GNU grep is required for this script" + echo_err "You can install it using the following command:" + echo_err "" + echo_err "brew install grep --with-default-names" + return 1 + fi + + if [[ -z "$(type -t git-changelog)" ]] + then + echo_err "git-extras packages is not installed." + echo_err "You can install it using the following command:" + echo_err "" + echo_err "brew install git-extras" + return 1 + fi +} + +# Replace old string only if it is present in the file, otherwise return 1 +function safe_replace +{ + local old=$1 + local new=$2 + local file=$3 + + grep -q "${old}" "${file}" || { echo_err "${old} was not found in ${file}"; return 1; } + + ${CMD_PREFIX} sed -E -i '.bak' "${QUOTE}s/${old}/${new}/${QUOTE}" "${file}" +} + +function update_version +{ + if [ -z "${NEW_VERSION}" ]; then + usage; return 1 + fi + + # Enter git root + pushd $(git rev-parse --show-toplevel) + + local current_version=`grep -oiP '(?<="version": ")([0-9.]+)(?=")' composer.json` + + if [ -z "${current_version}" ]; then + echo_err "Failed getting current version, please check directory structure and/or contact developer" + return 1 + fi + + # Use literal dot character in regular expression + local current_version_re=${current_version//./\\.} + + echo "# Current version is: ${current_version}" + echo "# New version is: ${NEW_VERSION}" + + ver_lte "${NEW_VERSION}" "${current_version}" && { echo_err "New version is not greater than current version"; return 1; } + + # Add a quote if this is a dry run + QUOTE=${CMD_PREFIX:+"'"} + + safe_replace "\"version\": \"${current_version_re}\""\ + "\"version\": \"${NEW_VERSION}\""\ + composer.json\ + || return 1 + safe_replace "const VERSION = \"${current_version_re}\""\ + "const VERSION = \"${NEW_VERSION}\""\ + src/Cloudinary.php\ + || return 1 + + ${CMD_PREFIX} git changelog -t ${NEW_VERSION} || true + + echo "" + echo "# After editing CHANGELOG.md, optionally review changes and issue these commands:" + echo git add composer.json src/Cloudinary.php CHANGELOG.md + echo git commit -m "\"Version ${NEW_VERSION}\"" + echo sed -e "'1,/^${NEW_VERSION//./\\.}/d'" \ + -e "'/^=/d'" \ + -e "'/^$/d'" \ + -e "'/^[0-9]/,\$d'" \ + CHANGELOG.md \ + \| git tag -a "'${NEW_VERSION}'" --file=- + + # Don't run those commands on dry run + [ -n "${CMD_PREFIX}" ] && { popd; return 0; } + + echo "" + read -p "Run the above commands automatically? (y/N): " confirm && [[ ${confirm} == [yY] || ${confirm} == [yY][eE][sS] ]] || { popd; return 0; } + + git add composer.json src/Cloudinary.php CHANGELOG.md + git commit -m "Version ${NEW_VERSION}" + sed -e "1,/^${NEW_VERSION//./\\.}/d" \ + -e "/^=/d" \ + -e "/^$/d" \ + -e "/^[0-9]/,\$d" \ + CHANGELOG.md \ + | git tag -a "${NEW_VERSION}" --file=- + + popd +} + +verify_dependencies +process_arguments $* +update_version diff --git a/lib/CloudinaryExtension/vendor/composer/ClassLoader.php b/lib/CloudinaryExtension/vendor/composer/ClassLoader.php new file mode 100644 index 0000000..fce8549 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/composer/ClassLoader.php @@ -0,0 +1,445 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/lib/CloudinaryExtension/vendor/composer/LICENSE b/lib/CloudinaryExtension/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/lib/CloudinaryExtension/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/lib/CloudinaryExtension/vendor/composer/autoload_classmap.php b/lib/CloudinaryExtension/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..18cbffc --- /dev/null +++ b/lib/CloudinaryExtension/vendor/composer/autoload_classmap.php @@ -0,0 +1,34 @@ + $vendorDir . '/cloudinary/cloudinary_php/src/Cloudinary.php', + 'CloudinaryField' => $vendorDir . '/cloudinary/cloudinary_php/src/CloudinaryField.php', + 'Cloudinary\\Api' => $vendorDir . '/cloudinary/cloudinary_php/src/Api.php', + 'Cloudinary\\Api\\AlreadyExists' => $vendorDir . '/cloudinary/cloudinary_php/src/Api/AlreadyExists.php', + 'Cloudinary\\Api\\AuthorizationRequired' => $vendorDir . '/cloudinary/cloudinary_php/src/Api/AuthorizationRequired.php', + 'Cloudinary\\Api\\BadRequest' => $vendorDir . '/cloudinary/cloudinary_php/src/Api/BadRequest.php', + 'Cloudinary\\Api\\Error' => $vendorDir . '/cloudinary/cloudinary_php/src/Api/Error.php', + 'Cloudinary\\Api\\GeneralError' => $vendorDir . '/cloudinary/cloudinary_php/src/Api/GeneralError.php', + 'Cloudinary\\Api\\NotAllowed' => $vendorDir . '/cloudinary/cloudinary_php/src/Api/NotAllowed.php', + 'Cloudinary\\Api\\NotFound' => $vendorDir . '/cloudinary/cloudinary_php/src/Api/NotFound.php', + 'Cloudinary\\Api\\RateLimited' => $vendorDir . '/cloudinary/cloudinary_php/src/Api/RateLimited.php', + 'Cloudinary\\Api\\Response' => $vendorDir . '/cloudinary/cloudinary_php/src/Api/Response.php', + 'Cloudinary\\AuthToken' => $vendorDir . '/cloudinary/cloudinary_php/src/AuthToken.php', + 'Cloudinary\\Cache\\Adapter\\CacheAdapter' => $vendorDir . '/cloudinary/cloudinary_php/src/Cache/Adapter/CacheAdapter.php', + 'Cloudinary\\Cache\\Adapter\\KeyValueCacheAdapter' => $vendorDir . '/cloudinary/cloudinary_php/src/Cache/Adapter/KeyValueCacheAdapter.php', + 'Cloudinary\\Cache\\ResponsiveBreakpointsCache' => $vendorDir . '/cloudinary/cloudinary_php/src/Cache/ResponsiveBreakpointsCache.php', + 'Cloudinary\\Cache\\Storage\\FileSystemKeyValueStorage' => $vendorDir . '/cloudinary/cloudinary_php/src/Cache/Storage/FileSystemKeyValueStorage.php', + 'Cloudinary\\Cache\\Storage\\KeyValueStorage' => $vendorDir . '/cloudinary/cloudinary_php/src/Cache/Storage/KeyValueStorage.php', + 'Cloudinary\\Error' => $vendorDir . '/cloudinary/cloudinary_php/src/Error.php', + 'Cloudinary\\HttpClient' => $vendorDir . '/cloudinary/cloudinary_php/src/HttpClient.php', + 'Cloudinary\\PreloadedFile' => $vendorDir . '/cloudinary/cloudinary_php/src/PreloadedFile.php', + 'Cloudinary\\Search' => $vendorDir . '/cloudinary/cloudinary_php/src/Search.php', + 'Cloudinary\\SignatureVerifier' => $vendorDir . '/cloudinary/cloudinary_php/src/SignatureVerifier.php', + 'Cloudinary\\Uploader' => $vendorDir . '/cloudinary/cloudinary_php/src/Uploader.php', + 'Cloudinary\\Utils\\Singleton' => $vendorDir . '/cloudinary/cloudinary_php/src/Utils/Singleton.php', +); diff --git a/lib/CloudinaryExtension/vendor/composer/autoload_files.php b/lib/CloudinaryExtension/vendor/composer/autoload_files.php new file mode 100644 index 0000000..ff62b44 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/composer/autoload_files.php @@ -0,0 +1,10 @@ + $vendorDir . '/cloudinary/cloudinary_php/src/Helpers.php', +); diff --git a/lib/CloudinaryExtension/vendor/composer/autoload_namespaces.php b/lib/CloudinaryExtension/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/lib/CloudinaryExtension/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit5c25c0b3ade6fd5d8c71f7583081bcfe::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit5c25c0b3ade6fd5d8c71f7583081bcfe::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire5c25c0b3ade6fd5d8c71f7583081bcfe($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire5c25c0b3ade6fd5d8c71f7583081bcfe($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/lib/CloudinaryExtension/vendor/composer/autoload_static.php b/lib/CloudinaryExtension/vendor/composer/autoload_static.php new file mode 100644 index 0000000..0bcd46e --- /dev/null +++ b/lib/CloudinaryExtension/vendor/composer/autoload_static.php @@ -0,0 +1,48 @@ + __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Helpers.php', + ); + + public static $classMap = array ( + 'Cloudinary' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Cloudinary.php', + 'CloudinaryField' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/CloudinaryField.php', + 'Cloudinary\\Api' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Api.php', + 'Cloudinary\\Api\\AlreadyExists' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Api/AlreadyExists.php', + 'Cloudinary\\Api\\AuthorizationRequired' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Api/AuthorizationRequired.php', + 'Cloudinary\\Api\\BadRequest' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Api/BadRequest.php', + 'Cloudinary\\Api\\Error' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Api/Error.php', + 'Cloudinary\\Api\\GeneralError' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Api/GeneralError.php', + 'Cloudinary\\Api\\NotAllowed' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Api/NotAllowed.php', + 'Cloudinary\\Api\\NotFound' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Api/NotFound.php', + 'Cloudinary\\Api\\RateLimited' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Api/RateLimited.php', + 'Cloudinary\\Api\\Response' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Api/Response.php', + 'Cloudinary\\AuthToken' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/AuthToken.php', + 'Cloudinary\\Cache\\Adapter\\CacheAdapter' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Cache/Adapter/CacheAdapter.php', + 'Cloudinary\\Cache\\Adapter\\KeyValueCacheAdapter' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Cache/Adapter/KeyValueCacheAdapter.php', + 'Cloudinary\\Cache\\ResponsiveBreakpointsCache' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Cache/ResponsiveBreakpointsCache.php', + 'Cloudinary\\Cache\\Storage\\FileSystemKeyValueStorage' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Cache/Storage/FileSystemKeyValueStorage.php', + 'Cloudinary\\Cache\\Storage\\KeyValueStorage' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Cache/Storage/KeyValueStorage.php', + 'Cloudinary\\Error' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Error.php', + 'Cloudinary\\HttpClient' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/HttpClient.php', + 'Cloudinary\\PreloadedFile' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/PreloadedFile.php', + 'Cloudinary\\Search' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Search.php', + 'Cloudinary\\SignatureVerifier' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/SignatureVerifier.php', + 'Cloudinary\\Uploader' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Uploader.php', + 'Cloudinary\\Utils\\Singleton' => __DIR__ . '/..' . '/cloudinary/cloudinary_php/src/Utils/Singleton.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->classMap = ComposerStaticInit5c25c0b3ade6fd5d8c71f7583081bcfe::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/lib/CloudinaryExtension/vendor/composer/installed.json b/lib/CloudinaryExtension/vendor/composer/installed.json new file mode 100644 index 0000000..cf78d3f --- /dev/null +++ b/lib/CloudinaryExtension/vendor/composer/installed.json @@ -0,0 +1,56 @@ +[ + { + "name": "cloudinary/cloudinary_php", + "version": "1.16.0", + "version_normalized": "1.16.0.0", + "source": { + "type": "git", + "url": "https://github.com/cloudinary/cloudinary_php.git", + "reference": "e33619e48ea8fa0350c007d640777091bfa4fdbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cloudinary/cloudinary_php/zipball/e33619e48ea8fa0350c007d640777091bfa4fdbc", + "reference": "e33619e48ea8fa0350c007d640777091bfa4fdbc", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*" + }, + "time": "2019-11-28T15:57:33+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "src" + ], + "files": [ + "src/Helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cloudinary", + "homepage": "https://github.com/cloudinary/cloudinary_php/graphs/contributors" + } + ], + "description": "Cloudinary PHP SDK", + "homepage": "https://github.com/cloudinary/cloudinary_php", + "keywords": [ + "cdn", + "cloud", + "cloudinary", + "image management", + "sdk" + ] + } +] diff --git a/modman b/modman index f9cb469..d35e6e6 100644 --- a/modman +++ b/modman @@ -6,5 +6,4 @@ app/design/frontend/base/default/template/cloudinary app/design/frontend/base/de app/etc/modules/Cloudinary_Cloudinary.xml app/etc/modules/Cloudinary_Cloudinary.xml js/cloudinary js/cloudinary lib/CloudinaryExtension lib/CloudinaryExtension -lib/Cloudinary lib/Cloudinary skin/adminhtml/default/default/images/cloudinary skin/adminhtml/default/default/images/cloudinary diff --git a/var/connect/Cloudinary_Cloudinary-3.0.3.tgz b/var/connect/Cloudinary_Cloudinary-3.0.3.tgz deleted file mode 100644 index 009e151..0000000 Binary files a/var/connect/Cloudinary_Cloudinary-3.0.3.tgz and /dev/null differ diff --git a/var/connect/Cloudinary_Cloudinary-3.1.0.tgz b/var/connect/Cloudinary_Cloudinary-3.1.0.tgz new file mode 100644 index 0000000..8e85e11 Binary files /dev/null and b/var/connect/Cloudinary_Cloudinary-3.1.0.tgz differ diff --git a/var/connect/Cloudinary_Cloudinary.xml b/var/connect/Cloudinary_Cloudinary.xml index 4b8002c..61938f5 100644 --- a/var/connect/Cloudinary_Cloudinary.xml +++ b/var/connect/Cloudinary_Cloudinary.xml @@ -1,5 +1,5 @@ <_> - iuUHd46ajY36y3ji + 3F4BbWrCQHzufos5 Cloudinary_Cloudinary community @@ -9,7 +9,7 @@ Cloudinary supercharges your images! Upload images to the cloud, deliver optimized via a fast CDN, perform smart resizing and apply effects. MIT License (MITL) - 3.0.3 + 3.1.0 stable - Match MEQP1 coding standards @@ -41,7 +41,7 @@ - + @@ -62,7 +62,6 @@ magecommunity mageetc magelib - magelib magedesign magedesign mageskin @@ -72,7 +71,6 @@ Cloudinary/Cloudinary modules/Cloudinary_Cloudinary.xml CloudinaryExtension - Cloudinary adminhtml/default/default/layout/cloudinary adminhtml/default/default/template/cloudinary adminhtml/default/default/images/cloudinary @@ -85,7 +83,6 @@ dir dir dir - dir @@ -95,7 +92,6 @@ - @@ -105,7 +101,6 @@ - diff --git a/var/connect/package.xml b/var/connect/package.xml index 92cdb4b..85e1bb9 100644 --- a/var/connect/package.xml +++ b/var/connect/package.xml @@ -1,7 +1,7 @@ Cloudinary_Cloudinary - 3.0.3 + 3.1.0 stable MIT License (MITL) community @@ -11,9 +11,9 @@ - Match MEQP1 coding standards Cloudinarycloudinaryaccounts+magento@cloudinary.com - 2019-10-10 - - + 2020-01-16 + + 5.4.07.2.2