From 2a519a47c930739e06577888dbc08d09f2d71206 Mon Sep 17 00:00:00 2001
From: Alon Hammerman ";
foreach ($array as $key => $value) {
- if ($key != 'class') {
- if ($key == 'url' || $key == 'secure_url') {
- $display_value = '"' . $value . '"';
- } else {
- $display_value = json_encode($value);
- }
- echo "
";
error_reporting($saved_error_reporting);
}
function create_photo_model($options = array()) {
- $photo = \R::dispense('photo');
-
- # Add metadata we want to keep:
- $photo->created_at = \R::isoDateTime();
- foreach ( $options as $key => $value ) {
- if ($key != 'tags') {
- $photo->{$key} = $value;
- }
- }
- $id = \R::store($photo);
- }
+ $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());
+
+ $id = \R::store($photo);
+ }
}
?>
From dae0ba1b8a5c1684e08dfd035c73f3254f771683 Mon Sep 17 00:00:00 2001
From: Alon Hammerman ";
- }
+ if ($key != 'class') {
+ if ($key == 'url' || $key == 'secure_url') {
+ $display_value = '"' . $value . '"';
+ } else {
+ $display_value = json_encode($value);
+ }
+ echo "" . $key . ": " . $display_value . " ";
+ }
}
echo "" . $key . ": " . $display_value . "
+ : + '{$url}'" + ); ?> +
+ 0): + echo $this->element('exception_stack_trace'); +endif; +?> diff --git a/samples/PhotoAlbumCake/View/Errors/error500.ctp b/samples/PhotoAlbumCake/View/Errors/error500.ctp new file mode 100644 index 00000000..9d0161d2 --- /dev/null +++ b/samples/PhotoAlbumCake/View/Errors/error500.ctp @@ -0,0 +1,20 @@ + + ++ : + +
+ 0): + echo $this->element('exception_stack_trace'); +endif; +?> diff --git a/samples/PhotoAlbumCake/View/Helper/AppHelper.php b/samples/PhotoAlbumCake/View/Helper/AppHelper.php new file mode 100644 index 00000000..7b734247 --- /dev/null +++ b/samples/PhotoAlbumCake/View/Helper/AppHelper.php @@ -0,0 +1,26 @@ + + + + +This email was sent using the CakePHP Framework
+ + \ No newline at end of file diff --git a/samples/PhotoAlbumCake/View/Layouts/Emails/text/default.ctp b/samples/PhotoAlbumCake/View/Layouts/Emails/text/default.ctp new file mode 100644 index 00000000..a84762ea --- /dev/null +++ b/samples/PhotoAlbumCake/View/Layouts/Emails/text/default.ctp @@ -0,0 +1,22 @@ + +fetch('content'); ?> + +This email was sent using the CakePHP Framework, http://cakephp.org. diff --git a/samples/PhotoAlbumCake/View/Layouts/ajax.ctp b/samples/PhotoAlbumCake/View/Layouts/ajax.ctp new file mode 100644 index 00000000..8b06e4dc --- /dev/null +++ b/samples/PhotoAlbumCake/View/Layouts/ajax.ctp @@ -0,0 +1,11 @@ + +fetch('content'); ?> diff --git a/samples/PhotoAlbumCake/View/Layouts/default.ctp b/samples/PhotoAlbumCake/View/Layouts/default.ctp new file mode 100644 index 00000000..98a67ab1 --- /dev/null +++ b/samples/PhotoAlbumCake/View/Layouts/default.ctp @@ -0,0 +1,53 @@ + + + + + Html->charset(); ?> ++ + 1) Help me configure it + 2) I don't / can't use URL rewriting +
+ ++=')): + echo ''; + echo __d('cake_dev', 'Your version of PHP is 5.2.8 or higher.'); + echo ''; + else: + echo ''; + echo __d('cake_dev', 'Your version of PHP is too low. You need PHP 5.2.8 or higher to use CakePHP.'); + echo ''; + endif; +?> +
++ '; + echo __d('cake_dev', 'Your tmp directory is writable.'); + echo ''; + else: + echo ''; + echo __d('cake_dev', 'Your tmp directory is NOT writable.'); + echo ''; + endif; + ?> +
++ '; + echo __d('cake_dev', 'The %s is being used for core caching. To change the config edit %s', ''. $settings['engine'] . 'Engine', 'APP/Config/core.php'); + echo ''; + else: + echo ''; + echo __d('cake_dev', 'Your cache is NOT working. Please check the settings in %s', 'APP/Config/core.php'); + echo ''; + endif; + ?> +
+
+ ';
+ echo __d('cake_dev', 'Your database configuration file is present.');
+ $filePresent = true;
+ echo '';
+ else:
+ echo '';
+ echo __d('cake_dev', 'Your database configuration file is NOT present.');
+ echo '
';
+ echo __d('cake_dev', 'Rename %s to %s', 'APP/Config/database.php.default', 'APP/Config/database.php');
+ echo '';
+ endif;
+ ?>
+
+ isConnected()):
+ echo '';
+ echo __d('cake_dev', 'CakePHP is able to connect to the database.');
+ echo '';
+ else:
+ echo '';
+ echo __d('cake_dev', 'CakePHP is NOT able to connect to the database.');
+ echo '
';
+ echo $errorMsg;
+ echo '';
+ endif;
+ ?>
+
--enable-unicode-properties when configuring');
+ echo '';
+ endif;
+?>
+
+
+ ';
+ echo __d('cake_dev', 'DebugKit plugin is present');
+ echo '';
+ else:
+ echo '';
+ echo __d('cake_dev', 'DebugKit is not installed. It will help you inspect and debug different aspects of your application.');
+ echo '
';
+ echo __d('cake_dev', 'You can install it from %s', $this->Html->link('github', 'https://github.com/cakephp/debug_kit'));
+ echo '';
+ endif;
+ ?>
+
+
+To change its layout, edit: %s.
+You can also add some CSS styles for your pages at: %s.',
+ 'APP/View/Pages/home.ctp', 'APP/View/Layouts/default.ctp', 'APP/webroot/css');
+?>
+
+ Html->link( + sprintf('%s %s', __d('cake_dev', 'New'), __d('cake_dev', 'CakePHP 2.0 Docs')), + 'http://book.cakephp.org/2.0/en/', + array('target' => '_blank', 'escape' => false) + ); + ?> +
++ Html->link( + __d('cake_dev', 'The 15 min Blog Tutorial'), + 'http://book.cakephp.org/2.0/en/tutorials-and-examples/blog/blog.html', + array('target' => '_blank', 'escape' => false) + ); + ?> +
+ + ++
+ +
++ +
+ +DK79JiR5fMQk5Qsz~iA0Kwj3krEQBhIR(a{tNg-WH;XtbD^nAq4@I-MRD z7Z)EN&tNd#zkknUGFdEELPA1fV&aDnACi)il9Q8DQc_Y=Q`6GY($mvFe*Bn`k-=uO zGcz+e91fSu&C1Hk&d$!s$>H&Md_F%nH#aXYPaqI{`t&J3KVK*m78DeSM54mN!lI(0 z;^N|xl9JNW(z3F$^78VEii*n0%BrfW>gwv6nwr|$+Pb>B`uh5YhK9z*#-^sG=H}*> zmX_Am*0#2`_V)IUj*iaG&aSSm?(S}}SlrXo)7#tI*Vp&?^XD&LzV!F^4-52q~$`a_L84UdB=6VyZ=u9nnig?j|*#Wx)G#h*aePfzjUYo@M;4}Fb&*iuQMNlT)P zH1r_DmZyA5)S=yZ1P7iME0&D<)aAv^j SzbA!qhX89U8%v>u2jO4XOSDb^ literal 0 HcmV?d00001 diff --git a/samples/PhotoAlbumCake/webroot/index.php b/samples/PhotoAlbumCake/webroot/index.php new file mode 100644 index 00000000..5ad4986f --- /dev/null +++ b/samples/PhotoAlbumCake/webroot/index.php @@ -0,0 +1,101 @@ +dispatch( + new CakeRequest(), + new CakeResponse() +); From dbf12581ff774ce5aa1131e1b61af4ac3587dfc2 Mon Sep 17 00:00:00 2001 From: Alon Hammerman Date: Fri, 22 Nov 2013 02:17:02 -0600 Subject: [PATCH 05/24] php framework - cloudinary_url supports cloudinary identifier as source --- src/Cloudinary.php | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Cloudinary.php b/src/Cloudinary.php index 12cf1111..85ec18ae 100644 --- a/src/Cloudinary.php +++ b/src/Cloudinary.php @@ -5,7 +5,7 @@ class Cloudinary { const OLD_AKAMAI_SHARED_CDN = "cloudinary-a.akamaihd.net"; const AKAMAI_SHARED_CDN = "res.cloudinary.com"; const SHARED_CDN = "res.cloudinary.com"; - + private static $config = NULL; public static $JS_CONFIG_PARAMS = array("api_key", "cloud_name", "private_cdn", "secure_distribution", "cdn_subdomain"); @@ -153,6 +153,7 @@ public static function generate_transformation_string(&$options=array()) { // Warning: $options are being destructively updated! public static function cloudinary_url($source, &$options=array()) { + $source = self::check_identifier_source($source, $options); $type = Cloudinary::option_consume($options, "type", "upload"); if ($type == "fetch" && !isset($options["fetch_format"])) { @@ -210,6 +211,25 @@ public static function cloudinary_url($source, &$options=array()) { $type, $transformation, $version ? "v" . $version : "", $source))); } + // [ /][ /][v /] [. ][# ] + // Warning: $options are being destructively updated! + public static function check_identifier_source($source, &$options=array()) { + $IDENTIFIER_RE = "~" . + "^(?:([^/]+)/)??(?:([^/]+)/)??(?:v(\\d+)/)?" . + "(?:([^#/]+?)(?:\\.([^.#/]+))?)(?:#([^/]+))?$" . "~"; + $matches = array(); + if (strstr(':', $source) !== false || !preg_match($IDENTIFIER_RE, $source, $matches)) { + return $source; + } + $optionNames = array('resource_type', 'type', 'version', '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'=>"'", '%28'=>'(', '%29'=>')', '%3A'=>':', '%2F'=>'/'); From ca200e3a6f934db224b1f6153eee1e2964e21002 Mon Sep 17 00:00:00 2001 From: Alon Hammerman Date: Fri, 22 Nov 2013 20:55:30 -0600 Subject: [PATCH 06/24] php framework - add extended_identifier to PreloadedFile --- src/Uploader.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Uploader.php b/src/Uploader.php index cc14a106..25bd9e4d 100644 --- a/src/Uploader.php +++ b/src/Uploader.php @@ -283,7 +283,10 @@ 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; } From ca5aba6d186d6c1eade20ac0d02dd103e4a63a49 Mon Sep 17 00:00:00 2001 From: Alon Hammerman Date: Tue, 19 Nov 2013 22:53:07 -0600 Subject: [PATCH 07/24] cake_plugin - add CloudinaryBehavior --- .../CloudinaryCake/Lib/CloudinaryField.php | 51 +++++++ .../Model/Behavior/CloudinaryBehavior.php | 137 ++++++++++++++++++ src/Uploader.php | 2 - 3 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 cake_plugin/CloudinaryCake/Lib/CloudinaryField.php create mode 100644 cake_plugin/CloudinaryCake/Model/Behavior/CloudinaryBehavior.php diff --git a/cake_plugin/CloudinaryCake/Lib/CloudinaryField.php b/cake_plugin/CloudinaryCake/Lib/CloudinaryField.php new file mode 100644 index 00000000..d9ab8883 --- /dev/null +++ b/cake_plugin/CloudinaryCake/Lib/CloudinaryField.php @@ -0,0 +1,51 @@ +identifier = $identifier; + } + + public function __toString() { + return explode("#", $this->identifier)[0]; + } + + public function url($options = array()) { + if (!$this->identifier) { + // TODO: Error? + return; + } + return cloudinary_url($this->identifier, $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->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/cake_plugin/CloudinaryCake/Model/Behavior/CloudinaryBehavior.php b/cake_plugin/CloudinaryCake/Model/Behavior/CloudinaryBehavior.php new file mode 100644 index 00000000..3d11f58f --- /dev/null +++ b/cake_plugin/CloudinaryCake/Model/Behavior/CloudinaryBehavior.php @@ -0,0 +1,137 @@ +settings[$Model->alias])) { + $this->settings[$Model->alias] = array(/* default values */); + } + $this->settings[$Model->alias] = array_merge( + $this->settings[$Model->alias], (array)$settings); + $Model->Cloudinary = array(); + } + + public function cleanup(Model $Model) { + error_log("CloudinaryBehavior::cleanup(): "); + } + + /// Callbacks /// + public function afterFind(Model $Model, $results, $primary = false) { + error_log("CloudinaryBehavior::afterFind()"); + + $fieldNames = $this->relevantFields($Model); + if (!$fieldNames) { + return $results; + } + + foreach ($results as &$result) { + foreach ($fieldNames as $fieldName) { + $this->updateCloudinaryField($Model, $fieldName, $result); + } + } + return $results; + } + + public function beforeSave(Model $Model, $options = array()) { + foreach ($this->relevantFields($Model, $options) as $fieldName) { + $this->saveCloudinaryField($Model, $fieldName); + } + return true; + } + + public function beforeValidate(Model $Model, $options = array()) { + error_log("CloudinaryBehavior::beforeValidate()"); + foreach ($this->relevantFields($Model, $options) as $fieldName) { + $field = @$Model->data[$Model->alias][$fieldName]; + if (is_string($field) && $field) { + if (!(new CloudinaryField($field))->verify()) { + $Model->invalidate($fieldName, "Bad cloudinary signature!"); + error_log("CloudinaryBehavior::beforeValidate(): Error in field " . $fieldName . " with data: " . $field); + return false; + } + } + } + return true; + } + + /// Methods + private function createCloudinaryField(Model $Model, $fieldName, $source=NULL) { + error_log("CloudinaryBehavior::createCloudinaryField(): "); + $source = $source ? $source : $Model->data; + return new CloudinaryField(isset($source[$Model->alias][$fieldName]) ? + $source[$Model->alias][$fieldName] : ""); + } + + private function updateCloudinaryField(Model $Model, $fieldName, &$data=NULL) { + $source =& $data ? $data : $Model->data; + if (isset($source[$Model->alias][$fieldName]) && $source[$Model->alias][$fieldName] instanceof CloudinaryField) { + error_log("CloudinaryBehavior::updateCloudinaryField - not updating again field '" . $fieldName . "' of " . $Model); + return; + } + error_log("CloudinaryBehavior::updateCloudinaryField - updating field '" . $fieldName . "' of " . $Model->alias); + $source[$Model->alias][$fieldName] = $this->createCloudinaryField($Model, $fieldName, $source); + } + + private function saveCloudinaryField(Model $Model, $fieldName) { + $field = @$Model->data[$Model->alias][$fieldName]; + $ret = NULL; + if ($field instanceof CloudinaryField) { + return; + } elseif (!$field) { + $ret = new CloudinaryField(); + } elseif (is_string($field)) { + $ret = new CloudinaryField($field); + // $ret->verify(); - Validate only in beforeValidate + } elseif (is_array($field) && isset($field['tmp_name'])) { + $ret = new CloudinaryField(); + $ret->upload($field['tmp_name']); + } else { + // TODO - handle file object? + throw new \Exception("Couldn't save cloudinary field '" . $Model->alias . ":" . $fieldName . + "' - unknown input: " . gettype($field)); + } + $Model->data[$Model->alias][$fieldName] = $ret; + } + + private function relevantFields(Model $Model, $options = array()) { + $cloudinaryFields = $this->settings[$Model->alias]['fields']; + if (!(isset($options['fieldList']) && $options['fieldList'])) { + return $cloudinaryFields; + } + return array_intersect($cloudinaryFields, $options['fieldList']); + } +} + +/* +Simplest usage: + Model: + class Photo extends AppModel { + public $actsAs = array('CloudinaryCake.Cloudinary' => array('fields' => array('cloudinaryIdentifier'))); + } + + Controller: + Find: + $photo = $this->Photo->find('first', $options); // returns CloudinaryField in + + Modify: + $photo['Photo']['cloudinaryIdentifier'].upload($new_file); + + Delete: + $photo['Photo']['cloudinaryIdentifier'].delete($new_file); + + Save: + Photo->save($photo); + + Save (from form): + Photo->save($this->request->data); // should work with file upload or identifier + + * Validate identifier upon save + * +*/ diff --git a/src/Uploader.php b/src/Uploader.php index 25bd9e4d..5d470249 100644 --- a/src/Uploader.php +++ b/src/Uploader.php @@ -252,11 +252,9 @@ public function __construct($file_info) { $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"); } From b9ec83ebc6f0b3f161af1dab4387724bf6f8d4b8 Mon Sep 17 00:00:00 2001 From: Alon Hammerman Date: Sat, 23 Nov 2013 18:12:18 -0600 Subject: [PATCH 08/24] cake_plugin - CloudinaryBehavior change model's defaults in order for Model::create() to return a CloudinaryField in apropriate fields Note: Uses some monkey patching evil! --- .../Model/Behavior/CloudinaryBehavior.php | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/cake_plugin/CloudinaryCake/Model/Behavior/CloudinaryBehavior.php b/cake_plugin/CloudinaryCake/Model/Behavior/CloudinaryBehavior.php index 3d11f58f..6371763a 100644 --- a/cake_plugin/CloudinaryCake/Model/Behavior/CloudinaryBehavior.php +++ b/cake_plugin/CloudinaryCake/Model/Behavior/CloudinaryBehavior.php @@ -4,6 +4,11 @@ App::uses('CloudinaryField', 'CloudinaryCake.Lib'); class CloudinaryBehavior extends ModelBehavior { + public $settingsDefaults = array( + "fields" => array(), + "changeModelDefaults" => true + ); + public function __construct() { error_log("CloudinaryBehavior::__construct)"); } @@ -11,11 +16,14 @@ public function __construct() { public function setup(Model $Model, $settings = array()) { error_log("CloudinaryBehavior::setup(): "); if (!isset($this->settings[$Model->alias])) { - $this->settings[$Model->alias] = array(/* default values */); + $this->settings[$Model->alias] = $this->settingsDefaults; } $this->settings[$Model->alias] = array_merge( - $this->settings[$Model->alias], (array)$settings); - $Model->Cloudinary = array(); + $this->settings[$Model->alias], (array)$settings + ); + if ($this->settings[$Model->alias]['changeModelDefaults']) { + $this->changeModelDefaults($Model); + } } public function cleanup(Model $Model) { @@ -107,6 +115,31 @@ private function relevantFields(Model $Model, $options = array()) { } return array_intersect($cloudinaryFields, $options['fieldList']); } + + private static function modifyPropertyUsingForce($instance, $property, $newValue) { + if (version_compare(PHP_VERSION, '5.3.0') >= 0) { + $myClassReflection = new ReflectionClass(get_class($instance)); + $secret = $myClassReflection->getProperty($property); + $secret->setAccessible(true); + $secret->setValue($instance, $newValue); + } + } + + private function changeModelDefaults(Model $Model) { + $schema = $Model->schema(); + foreach ($this->relevantFields($Model) as $fieldName) { + $schema[$fieldName] = new CloneOnAccessArray($schema[$fieldName]); + $schema[$fieldName]['default'] = new CloudinaryField(); + } + self::modifyPropertyUsingForce($Model, "_schema", $schema); + } +} + +class CloneOnAccessArray extends ArrayObject { + public function offsetGet($offset) { + $ret = parent::offsetGet($offset); + return (is_object($ret)) ? clone $ret : $ret; + } } /* From 358399b2d3f39fe6d8cfbd0c7b7ac0160d18f330 Mon Sep 17 00:00:00 2001 From: Alon Hammerman Date: Fri, 22 Nov 2013 21:00:39 -0600 Subject: [PATCH 09/24] cake_plugin - use extended_identifier (from future 1.0.8) php framework --- cake_plugin/CloudinaryCake/Lib/CloudinaryField.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cake_plugin/CloudinaryCake/Lib/CloudinaryField.php b/cake_plugin/CloudinaryCake/Lib/CloudinaryField.php index d9ab8883..56d6727f 100644 --- a/cake_plugin/CloudinaryCake/Lib/CloudinaryField.php +++ b/cake_plugin/CloudinaryCake/Lib/CloudinaryField.php @@ -35,7 +35,7 @@ public function upload($file, $options = array()) { if ($this->verifyUpload && !$preloaded.is_valid()) { throw new \Exception("Error! Couldn't verify cloudinary response!"); } - $this->identifier = $preloaded->identifier(); + $this->identifier = $preloaded->extended_identifier(); } public function delete() { From ed01cfdada44ae9ec827542ff79fdddf7f0a9ca4 Mon Sep 17 00:00:00 2001 From: Alon Hammerman Date: Thu, 21 Nov 2013 16:46:13 -0600 Subject: [PATCH 10/24] cake_plugin - add CloudinaryHelper --- .../Model/Behavior/CloudinaryBehavior.php | 7 ++- .../View/Helper/CloudinaryHelper.php | 61 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 cake_plugin/CloudinaryCake/View/Helper/CloudinaryHelper.php diff --git a/cake_plugin/CloudinaryCake/Model/Behavior/CloudinaryBehavior.php b/cake_plugin/CloudinaryCake/Model/Behavior/CloudinaryBehavior.php index 6371763a..5cc0b53d 100644 --- a/cake_plugin/CloudinaryCake/Model/Behavior/CloudinaryBehavior.php +++ b/cake_plugin/CloudinaryCake/Model/Behavior/CloudinaryBehavior.php @@ -69,7 +69,12 @@ public function beforeValidate(Model $Model, $options = array()) { return true; } - /// Methods + /// Public Methods /// + public function cloudinaryFields(Model $Model) { + return $this->settings[$Model->alias]['fields']; + } + + /// Private Methods /// private function createCloudinaryField(Model $Model, $fieldName, $source=NULL) { error_log("CloudinaryBehavior::createCloudinaryField(): "); $source = $source ? $source : $Model->data; diff --git a/cake_plugin/CloudinaryCake/View/Helper/CloudinaryHelper.php b/cake_plugin/CloudinaryCake/View/Helper/CloudinaryHelper.php new file mode 100644 index 00000000..238fa50d --- /dev/null +++ b/cake_plugin/CloudinaryCake/View/Helper/CloudinaryHelper.php @@ -0,0 +1,61 @@ +cloudinaryFunctions)) { + return call_user_func_array($name, $args); + } + return parent::__call($name, $args); + } + + /// Automatically detect cloudinary fields on models that have declared them. + public function input($fieldName, $options = array()) { + $this->setEntity($fieldName); + $model = $this->_getModel($this->model()); + $fieldKey = $this->field(); + if ($model->hasMethod('cloudinaryFields') && in_array($fieldKey, $model->cloudinaryFields())) { + $options['type'] = 'file'; + } + return parent::input($fieldName, $options); + } + + public function cloudinary_includes($options = array()) { + foreach ($this->cloudinaryJSIncludes as $include) { + echo $this->Html->script($include, $options); + } + } + + /// Called for input() when type => direct_upload + public function direct_upload() { + $modelKey = $this->model(); + $fieldKey = $this->field(); + return \cl_image_upload_tag("data[" . $modelKey . "][" . $fieldKey . "]"); + } +} From daa260c9d3a950ab9017c094fa9ff9cff1fabbb1 Mon Sep 17 00:00:00 2001 From: Alon Hammerman Date: Sat, 23 Nov 2013 22:59:35 -0600 Subject: [PATCH 11/24] CloudinaryField - move from cake_plugin to src --- .../Config/IncludeCloudinary.php | 11 +++++++ .../Model/Behavior/CloudinaryBehavior.php | 18 +---------- .../View/Helper/CloudinaryHelper.php | 4 +-- src/Cloudinary.php | 12 +++++--- .../Lib => src}/CloudinaryField.php | 30 ++++++++++--------- 5 files changed, 38 insertions(+), 37 deletions(-) create mode 100644 cake_plugin/CloudinaryCake/Config/IncludeCloudinary.php rename {cake_plugin/CloudinaryCake/Lib => src}/CloudinaryField.php (56%) diff --git a/cake_plugin/CloudinaryCake/Config/IncludeCloudinary.php b/cake_plugin/CloudinaryCake/Config/IncludeCloudinary.php new file mode 100644 index 00000000..bd30b7f3 --- /dev/null +++ b/cake_plugin/CloudinaryCake/Config/IncludeCloudinary.php @@ -0,0 +1,11 @@ + true ); - public function __construct() { - error_log("CloudinaryBehavior::__construct)"); - } - public function setup(Model $Model, $settings = array()) { - error_log("CloudinaryBehavior::setup(): "); if (!isset($this->settings[$Model->alias])) { $this->settings[$Model->alias] = $this->settingsDefaults; } @@ -26,14 +21,8 @@ public function setup(Model $Model, $settings = array()) { } } - public function cleanup(Model $Model) { - error_log("CloudinaryBehavior::cleanup(): "); - } - /// Callbacks /// public function afterFind(Model $Model, $results, $primary = false) { - error_log("CloudinaryBehavior::afterFind()"); - $fieldNames = $this->relevantFields($Model); if (!$fieldNames) { return $results; @@ -55,13 +44,11 @@ public function beforeSave(Model $Model, $options = array()) { } public function beforeValidate(Model $Model, $options = array()) { - error_log("CloudinaryBehavior::beforeValidate()"); foreach ($this->relevantFields($Model, $options) as $fieldName) { $field = @$Model->data[$Model->alias][$fieldName]; if (is_string($field) && $field) { if (!(new CloudinaryField($field))->verify()) { $Model->invalidate($fieldName, "Bad cloudinary signature!"); - error_log("CloudinaryBehavior::beforeValidate(): Error in field " . $fieldName . " with data: " . $field); return false; } } @@ -76,7 +63,6 @@ public function cloudinaryFields(Model $Model) { /// Private Methods /// private function createCloudinaryField(Model $Model, $fieldName, $source=NULL) { - error_log("CloudinaryBehavior::createCloudinaryField(): "); $source = $source ? $source : $Model->data; return new CloudinaryField(isset($source[$Model->alias][$fieldName]) ? $source[$Model->alias][$fieldName] : ""); @@ -85,10 +71,8 @@ private function createCloudinaryField(Model $Model, $fieldName, $source=NULL) { private function updateCloudinaryField(Model $Model, $fieldName, &$data=NULL) { $source =& $data ? $data : $Model->data; if (isset($source[$Model->alias][$fieldName]) && $source[$Model->alias][$fieldName] instanceof CloudinaryField) { - error_log("CloudinaryBehavior::updateCloudinaryField - not updating again field '" . $fieldName . "' of " . $Model); return; } - error_log("CloudinaryBehavior::updateCloudinaryField - updating field '" . $fieldName . "' of " . $Model->alias); $source[$Model->alias][$fieldName] = $this->createCloudinaryField($Model, $fieldName, $source); } diff --git a/cake_plugin/CloudinaryCake/View/Helper/CloudinaryHelper.php b/cake_plugin/CloudinaryCake/View/Helper/CloudinaryHelper.php index 238fa50d..21e3e271 100644 --- a/cake_plugin/CloudinaryCake/View/Helper/CloudinaryHelper.php +++ b/cake_plugin/CloudinaryCake/View/Helper/CloudinaryHelper.php @@ -1,7 +1,7 @@ setEntity($fieldName); $model = $this->_getModel($this->model()); $fieldKey = $this->field(); - if ($model->hasMethod('cloudinaryFields') && in_array($fieldKey, $model->cloudinaryFields())) { + if (!@$options['type'] && $model->hasMethod('cloudinaryFields') && in_array($fieldKey, $model->cloudinaryFields())) { $options['type'] = 'file'; } return parent::input($fieldName, $options); diff --git a/src/Cloudinary.php b/src/Cloudinary.php index 85ec18ae..0150ef70 100644 --- a/src/Cloudinary.php +++ b/src/Cloudinary.php @@ -153,7 +153,7 @@ public static function generate_transformation_string(&$options=array()) { // Warning: $options are being destructively updated! public static function cloudinary_url($source, &$options=array()) { - $source = self::check_identifier_source($source, $options); + $source = self::check_cloudinary_field($source, $options); $type = Cloudinary::option_consume($options, "type", "upload"); if ($type == "fetch" && !isset($options["fetch_format"])) { @@ -213,17 +213,21 @@ public static function cloudinary_url($source, &$options=array()) { // [ /][ /][v /] [. ][# ] // Warning: $options are being destructively updated! - public static function check_identifier_source($source, &$options=array()) { + public static function check_cloudinary_field($source, &$options=array()) { $IDENTIFIER_RE = "~" . "^(?:([^/]+)/)??(?:([^/]+)/)??(?:v(\\d+)/)?" . "(?:([^#/]+?)(?:\\.([^.#/]+))?)(?:#([^/]+))?$" . "~"; $matches = array(); - if (strstr(':', $source) !== false || !preg_match($IDENTIFIER_RE, $source, $matches)) { + if (!(is_object($source) && method_exists($source, 'identifier') && $source->identifier())) { + return $source; + } + $identifier = $source->identifier(); + if (strstr(':', $identifier) !== false || !preg_match($IDENTIFIER_RE, $identifier, $matches)) { return $source; } $optionNames = array('resource_type', 'type', 'version', 'public_id', 'format'); foreach ($optionNames as $index => $optionName) { - if ($matches[$index+1]) { + if (@$matches[$index+1]) { $options[$optionName] = $matches[$index+1]; } } diff --git a/cake_plugin/CloudinaryCake/Lib/CloudinaryField.php b/src/CloudinaryField.php similarity index 56% rename from cake_plugin/CloudinaryCake/Lib/CloudinaryField.php rename to src/CloudinaryField.php index 56d6727f..e758c4b9 100644 --- a/cake_plugin/CloudinaryCake/Lib/CloudinaryField.php +++ b/src/CloudinaryField.php @@ -3,29 +3,31 @@ * Manages access to a cloudinary image as a field */ -require_once '../../../src/Cloudinary.php'; -require_once '../../../src/Uploader.php'; +require_once 'Cloudinary.php'; +require_once 'Uploader.php'; -class CloudinaryField extends Object { - private $identifier = NULL; - private $autoSave = false; +class CloudinaryField { + private $_identifier = NULL; private $verifyUpload = false; public function __construct($identifier = "") { - error_log("CloudinaryField::__construct - " . $identifier); - $this->identifier = $identifier; + $this->_identifier = $identifier; } public function __toString() { - return explode("#", $this->identifier)[0]; + return explode("#", $this->identifier())[0]; + } + + public function identifier() { + return $this->_identifier; } public function url($options = array()) { - if (!$this->identifier) { + if (!$this->_identifier) { // TODO: Error? return; } - return cloudinary_url($this->identifier, $options); + return cloudinary_url($this, $options); } public function upload($file, $options = array()) { @@ -35,17 +37,17 @@ public function upload($file, $options = array()) { if ($this->verifyUpload && !$preloaded.is_valid()) { throw new \Exception("Error! Couldn't verify cloudinary response!"); } - $this->identifier = $preloaded->extended_identifier(); + $this->_identifier = $preloaded->extended_identifier(); } public function delete() { $options['return_error'] = false; - $ret = \Cloudinary\Uploader::destroy($this->identifier); - unset($this->identifier); + $ret = \Cloudinary\Uploader::destroy($this->_identifier); + unset($this->_identifier); } public function verify() { - $preloaded = new \Cloudinary\PreloadedFile($this->identifier); + $preloaded = new \Cloudinary\PreloadedFile($this->_identifier); return $preloaded->is_valid(); } } From c0beff94f2903adf9b8d471a81e29c75acc31282 Mon Sep 17 00:00:00 2001 From: Alon Hammerman Date: Mon, 25 Nov 2013 00:53:53 -0600 Subject: [PATCH 12/24] tests - add CloudinaryFieldTest [incomplete] --- tests/CloudinaryFieldTest.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/CloudinaryFieldTest.php diff --git a/tests/CloudinaryFieldTest.php b/tests/CloudinaryFieldTest.php new file mode 100644 index 00000000..0cecd337 --- /dev/null +++ b/tests/CloudinaryFieldTest.php @@ -0,0 +1,23 @@ +"test123", "secure_distribution" => NULL, "private_cdn" => FALSE)); + } + + public function test_cloudinary_url_from_cloudinary_field() { + // 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); + } +} + // [ /][ /][v /] [. ][# ] From 7b0651c5bcf998f6c776107f43fcffe516bb7692 Mon Sep 17 00:00:00 2001 From: Alon Hammerman Date: Tue, 5 Nov 2013 23:49:29 -0600 Subject: [PATCH 13/24] PhotoAlbumCake - Setup private config --- .gitignore | 2 +- samples/PhotoAlbumCake/.gitignore | 1 + samples/PhotoAlbumCake/Config/bootstrap.php | 9 +++++++++ samples/PhotoAlbumCake/Config/private.php.default | 9 +++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 samples/PhotoAlbumCake/Config/private.php.default diff --git a/.gitignore b/.gitignore index 3a4edf69..cb4728dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -.project +samples/PhotoAlbumCake/Config/private.php diff --git a/samples/PhotoAlbumCake/.gitignore b/samples/PhotoAlbumCake/.gitignore index a9a5aecf..e83b140c 100644 --- a/samples/PhotoAlbumCake/.gitignore +++ b/samples/PhotoAlbumCake/.gitignore @@ -1 +1,2 @@ tmp +Config/database.php diff --git a/samples/PhotoAlbumCake/Config/bootstrap.php b/samples/PhotoAlbumCake/Config/bootstrap.php index cc65b6d2..2d780096 100644 --- a/samples/PhotoAlbumCake/Config/bootstrap.php +++ b/samples/PhotoAlbumCake/Config/bootstrap.php @@ -43,6 +43,7 @@ * )); * */ +App::build(array('Plugin' => APP . '..' . DS . '..' . DS . 'cake_plugin' . DS)); /** * Custom Inflector rules, can be set to correctly pluralize or singularize table, model, controller names or whatever other @@ -100,3 +101,11 @@ )); CakePlugin::load('CloudinaryCake', array('bootstrap' => false, 'routes' => false)); +Configure::load('CloudinaryCake.IncludeCloudinary'); +try { + Configure::load('private'); + \Cloudinary::config(Configure::read('cloudinary')); +} catch (Exception $e) { + $result = Configure::configured('default'); + $this->assertTrue($result); +} diff --git a/samples/PhotoAlbumCake/Config/private.php.default b/samples/PhotoAlbumCake/Config/private.php.default new file mode 100644 index 00000000..637f3431 --- /dev/null +++ b/samples/PhotoAlbumCake/Config/private.php.default @@ -0,0 +1,9 @@ + array( + "cloud_name" => "CLOUD_NAME", + "api_key" => "API_KEY", + "api_secret" => "API_SECRET" + ) +); + From 59d3ae46ba9eae662061e5a76d40316b843ba48e Mon Sep 17 00:00:00 2001 From: Alon Hammerman Date: Wed, 6 Nov 2013 00:29:08 -0600 Subject: [PATCH 14/24] PhotoAlbumCake - bake all photo --- .../Controller/PhotosController.php | 103 ++++++++++++++++++ samples/PhotoAlbumCake/Model/photo.php | 9 ++ samples/PhotoAlbumCake/View/Photos/add.ctp | 18 +++ samples/PhotoAlbumCake/View/Photos/edit.ctp | 20 ++++ samples/PhotoAlbumCake/View/Photos/index.ctp | 46 ++++++++ samples/PhotoAlbumCake/View/Photos/view.ctp | 39 +++++++ 6 files changed, 235 insertions(+) create mode 100644 samples/PhotoAlbumCake/Controller/PhotosController.php create mode 100644 samples/PhotoAlbumCake/Model/photo.php create mode 100644 samples/PhotoAlbumCake/View/Photos/add.ctp create mode 100644 samples/PhotoAlbumCake/View/Photos/edit.ctp create mode 100644 samples/PhotoAlbumCake/View/Photos/index.ctp create mode 100644 samples/PhotoAlbumCake/View/Photos/view.ctp diff --git a/samples/PhotoAlbumCake/Controller/PhotosController.php b/samples/PhotoAlbumCake/Controller/PhotosController.php new file mode 100644 index 00000000..0ddbd3fa --- /dev/null +++ b/samples/PhotoAlbumCake/Controller/PhotosController.php @@ -0,0 +1,103 @@ +Photo->recursive = 0; + $this->set('photos', $this->Paginator->paginate()); + } + +/** + * view method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function view($id = null) { + if (!$this->Photo->exists($id)) { + throw new NotFoundException(__('Invalid photo')); + } + $options = array('conditions' => array('Photo.' . $this->Photo->primaryKey => $id)); + $this->set('photo', $this->Photo->find('first', $options)); + } + +/** + * add method + * + * @return void + */ + public function add() { + if ($this->request->is('post')) { + $this->Photo->create(); + if ($this->Photo->save($this->request->data)) { + $this->Session->setFlash(__('The photo has been saved.')); + return $this->redirect(array('action' => 'index')); + } else { + $this->Session->setFlash(__('The photo could not be saved. Please, try again.')); + } + } + } + +/** + * edit method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function edit($id = null) { + if (!$this->Photo->exists($id)) { + throw new NotFoundException(__('Invalid photo')); + } + if ($this->request->is(array('post', 'put'))) { + if ($this->Photo->save($this->request->data)) { + $this->Session->setFlash(__('The photo has been saved.')); + return $this->redirect(array('action' => 'index')); + } else { + $this->Session->setFlash(__('The photo could not be saved. Please, try again.')); + } + } else { + $options = array('conditions' => array('Photo.' . $this->Photo->primaryKey => $id)); + $this->request->data = $this->Photo->find('first', $options); + } + } + +/** + * delete method + * + * @throws NotFoundException + * @param string $id + * @return void + */ + public function delete($id = null) { + $this->Photo->id = $id; + if (!$this->Photo->exists()) { + throw new NotFoundException(__('Invalid photo')); + } + $this->request->onlyAllow('post', 'delete'); + if ($this->Photo->delete()) { + $this->Session->setFlash(__('The photo has been deleted.')); + } else { + $this->Session->setFlash(__('The photo could not be deleted. Please, try again.')); + } + return $this->redirect(array('action' => 'index')); + }} diff --git a/samples/PhotoAlbumCake/Model/photo.php b/samples/PhotoAlbumCake/Model/photo.php new file mode 100644 index 00000000..129e61b9 --- /dev/null +++ b/samples/PhotoAlbumCake/Model/photo.php @@ -0,0 +1,9 @@ + +Form->create('Photo'); ?> + +Form->end(__('Submit')); ?> + + + +diff --git a/samples/PhotoAlbumCake/View/Photos/edit.ctp b/samples/PhotoAlbumCake/View/Photos/edit.ctp new file mode 100644 index 00000000..44cc3cb2 --- /dev/null +++ b/samples/PhotoAlbumCake/View/Photos/edit.ctp @@ -0,0 +1,20 @@ ++ +
+- Html->link(__('List Photos'), array('action' => 'index')); ?>
++Form->create('Photo'); ?> + +Form->end(__('Submit')); ?> +++ +diff --git a/samples/PhotoAlbumCake/View/Photos/index.ctp b/samples/PhotoAlbumCake/View/Photos/index.ctp new file mode 100644 index 00000000..d6648638 --- /dev/null +++ b/samples/PhotoAlbumCake/View/Photos/index.ctp @@ -0,0 +1,46 @@ ++ +
+- Form->postLink(__('Delete'), array('action' => 'delete', $this->Form->value('Photo.id')), null, __('Are you sure you want to delete # %s?', $this->Form->value('Photo.id'))); ?>
+- Html->link(__('List Photos'), array('action' => 'index')); ?>
++ +++
++ + +Paginator->sort('id'); ?> +Paginator->sort('cloudinaryIdentifier'); ?> +Paginator->sort('moderated'); ?> +Paginator->sort('created'); ?> +Paginator->sort('updated'); ?> ++ + + ++ + + + + + Html->link(__('View'), array('action' => 'view', $photo['Photo']['id'])); ?> + Html->link(__('Edit'), array('action' => 'edit', $photo['Photo']['id'])); ?> + Form->postLink(__('Delete'), array('action' => 'delete', $photo['Photo']['id']), null, __('Are you sure you want to delete # %s?', $photo['Photo']['id'])); ?> + ++ Paginator->counter(array( + 'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}') + )); + ?>
++ Paginator->prev('< ' . __('previous'), array(), null, array('class' => 'prev disabled')); + echo $this->Paginator->numbers(array('separator' => '')); + echo $this->Paginator->next(__('next') . ' >', array(), null, array('class' => 'next disabled')); + ?> +++ +diff --git a/samples/PhotoAlbumCake/View/Photos/view.ctp b/samples/PhotoAlbumCake/View/Photos/view.ctp new file mode 100644 index 00000000..8fbbbfd9 --- /dev/null +++ b/samples/PhotoAlbumCake/View/Photos/view.ctp @@ -0,0 +1,39 @@ ++
+- Html->link(__('New Photo'), array('action' => 'add')); ?>
++ +++ +
+- + + +
+ +- + + +
+ +- + + +
+ +- + + +
+ +- + + +
++ +From e3f96646c08d0677a3df0c3b6e7632d8a8ef9c33 Mon Sep 17 00:00:00 2001 From: Alon Hammerman+
+- Html->link(__('Edit Photo'), array('action' => 'edit', $photo['Photo']['id'])); ?>
+- Form->postLink(__('Delete Photo'), array('action' => 'delete', $photo['Photo']['id']), null, __('Are you sure you want to delete # %s?', $photo['Photo']['id'])); ?>
+- Html->link(__('List Photos'), array('action' => 'index')); ?>
+- Html->link(__('New Photo'), array('action' => 'add')); ?>
+Date: Wed, 20 Nov 2013 18:06:29 -0600 Subject: [PATCH 15/24] PhotoAlbumCake - Add Cloudinary support to photo admin actions --- .../Controller/PhotosController.php | 1 + samples/PhotoAlbumCake/Model/photo.php | 4 +- samples/PhotoAlbumCake/View/Photos/add.ctp | 2 +- samples/PhotoAlbumCake/View/Photos/edit.ctp | 23 +- samples/PhotoAlbumCake/View/Photos/index.ctp | 12 +- .../webroot/js/canvas-to-blob.min.js | 1 + .../webroot/js/jquery.cloudinary.js | 443 ++++++ .../webroot/js/jquery.fileupload-image.js | 299 ++++ .../webroot/js/jquery.fileupload-process.js | 164 ++ .../webroot/js/jquery.fileupload-validate.js | 116 ++ .../webroot/js/jquery.fileupload.js | 1315 +++++++++++++++++ .../webroot/js/jquery.iframe-transport.js | 205 +++ .../webroot/js/jquery.ui.widget.js | 530 +++++++ .../webroot/js/load-image.min.js | 1 + 14 files changed, 3106 insertions(+), 10 deletions(-) create mode 100644 samples/PhotoAlbumCake/webroot/js/canvas-to-blob.min.js create mode 100644 samples/PhotoAlbumCake/webroot/js/jquery.cloudinary.js create mode 100644 samples/PhotoAlbumCake/webroot/js/jquery.fileupload-image.js create mode 100644 samples/PhotoAlbumCake/webroot/js/jquery.fileupload-process.js create mode 100644 samples/PhotoAlbumCake/webroot/js/jquery.fileupload-validate.js create mode 100644 samples/PhotoAlbumCake/webroot/js/jquery.fileupload.js create mode 100644 samples/PhotoAlbumCake/webroot/js/jquery.iframe-transport.js create mode 100644 samples/PhotoAlbumCake/webroot/js/jquery.ui.widget.js create mode 100644 samples/PhotoAlbumCake/webroot/js/load-image.min.js diff --git a/samples/PhotoAlbumCake/Controller/PhotosController.php b/samples/PhotoAlbumCake/Controller/PhotosController.php index 0ddbd3fa..d9876012 100644 --- a/samples/PhotoAlbumCake/Controller/PhotosController.php +++ b/samples/PhotoAlbumCake/Controller/PhotosController.php @@ -14,6 +14,7 @@ class PhotosController extends AppController { * @var array */ public $components = array('Paginator'); + public $helpers = array('Html', 'Form', 'CloudinaryCake.Cloudinary'); /** * index method diff --git a/samples/PhotoAlbumCake/Model/photo.php b/samples/PhotoAlbumCake/Model/photo.php index 129e61b9..c6ed8252 100644 --- a/samples/PhotoAlbumCake/Model/photo.php +++ b/samples/PhotoAlbumCake/Model/photo.php @@ -4,6 +4,6 @@ * photo Model * */ -class photo extends AppModel { - +class Photo extends AppModel { + public $actsAs = array('CloudinaryCake.Cloudinary' => array('fields' => array('cloudinaryIdentifier'))); } diff --git a/samples/PhotoAlbumCake/View/Photos/add.ctp b/samples/PhotoAlbumCake/View/Photos/add.ctp index c05fe56d..5068f9a9 100644 --- a/samples/PhotoAlbumCake/View/Photos/add.ctp +++ b/samples/PhotoAlbumCake/View/Photos/add.ctp @@ -1,5 +1,5 @@ -Form->create('Photo'); ?> +Form->create('Photo', array('type' => 'file')); ?>