diff --git a/wire/core/Functions.php b/wire/core/Functions.php index 2738b30e..67cf9844 100644 --- a/wire/core/Functions.php +++ b/wire/core/Functions.php @@ -758,6 +758,49 @@ function wireClassParents($className, $autoload = true) { return $a; } +/** + * Does given instance (or class) represent an instance of the given className (or class names)? + * + * @param object|string $instance Object instance to test (or string of its class name). + * @param string|array $className Class name or array of class names to test against. + * @param bool $autoload + * @return bool|string Returns one of the following: + * - boolean false if not an instance (whether $className argument is string or array). + * - boolean true if given a single $className (string) and $instance is an instance of it. + * - string of first matching class name if $className was an array of classes to test. + * + */ +function wireInstanceOf($instance, $className, $autoload = true) { + + if(is_array($className)) { + $returnClass = true; + $classNames = $className; + } else { + $returnClass = false; + $classNames = array($className); + } + + $matchClass = null; + $instanceParents = null; + + foreach($classNames as $className) { + $className = wireClassName($className, true); // with namespace + if(is_object($instance) && class_exists($className, $autoload)) { + if($instance instanceof $className) $matchClass = $className; + } else { + if(is_null($instanceParents)) { + $instanceParents = wireClassParents($instance, $autoload); + $instanceClass = is_string($instance) ? $instance : wireClassName($instance, true); + $instanceParents[$instanceClass] = 1; + } + if(isset($parents[$className])) $matchClass = $className; + } + if($matchClass !== null) break; + } + + return $returnClass ? $matchClass : ($matchClass !== null); +} + /** * ProcessWire namespace aware version of PHP's is_callable() function * diff --git a/wire/core/PagesExportImport.php b/wire/core/PagesExportImport.php index 393d59a7..8256b341 100644 --- a/wire/core/PagesExportImport.php +++ b/wire/core/PagesExportImport.php @@ -755,12 +755,8 @@ class PagesExportImport extends Wire { ); // fake-commit for more verbose testing of certain fieldtypes - $fakeCommit = false; - if(!$options['commit']) { - // we fake-commit Page refs so that validity is tested and errors known before commit - if($field->type instanceof FieldtypePage) $fakeCommit = true; - if($field->type instanceof FieldtypeRepeater) $fakeCommit = true; - } + $fakeCommitTypes = array('FieldtypePage', 'FieldtypeRepeater', 'FieldtypeComments'); + $fakeCommit = $options['commit'] || wireInstanceOf($field->type, $fakeCommitTypes); if($page->get('_importType') == 'create' && !$options['commit'] && !$fakeCommit) { // test import on a new page, so value will always be used @@ -1141,11 +1137,14 @@ class PagesExportImport extends Wire { $exportable = true; $reason = ''; - if($fieldtype instanceof FieldtypeFile) { - // files are allowed - - } else if($fieldtype instanceof FieldtypeRepeater) { - // repeaters are allowed + $extraType = wireInstanceOf($fieldtype, array( + 'FieldtypeFile', + 'FieldtypeRepeater', + 'FieldtypeComments', + )); + + if($extraType) { + // extra identified types are allowed } else if($fieldtype instanceof FieldtypeFieldsetOpen || $fieldtype instanceof FieldtypeFieldsetClose) { // fieldsets not exportable diff --git a/wire/modules/Fieldtype/FieldtypeComments/Comment.php b/wire/modules/Fieldtype/FieldtypeComments/Comment.php index 6616f6fe..6ba65ada 100644 --- a/wire/modules/Fieldtype/FieldtypeComments/Comment.php +++ b/wire/modules/Fieldtype/FieldtypeComments/Comment.php @@ -119,6 +119,14 @@ class Comment extends WireData { */ protected $_parent = null; + /** + * Quiet mode, when true actions like notification emails aren't triggered when applicable + * + * @var bool + * + */ + protected $quiet = false; + /** * Construct a Comment and set defaults * @@ -375,6 +383,22 @@ class Comment extends WireData { return $comments->renderStars(false, $options); } + /** + * Get or set quiet mode + * + * When quiet mode is active, comment additions/changes don't trigger notifications and such. + * + * @param bool $quiet Specify only if setting + * @return bool The current quiet mode + * + */ + public function quiet($quiet = null) { + if(is_bool($quiet)) $this->quiet = $quiet; + return $this->quiet; + } + + + } diff --git a/wire/modules/Fieldtype/FieldtypeComments/FieldtypeComments.module b/wire/modules/Fieldtype/FieldtypeComments/FieldtypeComments.module index d9722e4a..32828a91 100644 --- a/wire/modules/Fieldtype/FieldtypeComments/FieldtypeComments.module +++ b/wire/modules/Fieldtype/FieldtypeComments/FieldtypeComments.module @@ -246,6 +246,8 @@ class FieldtypeComments extends FieldtypeMulti { * */ protected function checkExistingComment(Page $page, Field $field, Comment $comment) { + + if($comment->quiet()) return; $submitSpam = false; $submitHam = false; @@ -312,7 +314,7 @@ class FieldtypeComments extends FieldtypeMulti { */ protected function checkNewComment(Page $page, Field $field, Comment $comment) { - if($comment->id) return; + if($comment->id || $comment->quiet()) return; $this->checkCommentCodes($comment); @@ -1511,5 +1513,185 @@ class FieldtypeComments extends FieldtypeMulti { parent::___renamedField($field, $prevName); } + /** + * Export Comment to array + * + * @param Comment|array $comment + * @return array + * + */ + protected function exportComment($comment) { + if(is_object($comment)) $comment = $comment->getArray(); + if(isset($comment['created_users_id'])) { + $u = $this->wire('users')->get((int) $comment['created_users_id']); + $comment['created_user'] = $u->name; + unset($comment['created_users_id']); + } + return $comment; + } + + /** + * Export Comments to array + * + * @param CommentArray|array $comments + * @return array + * + */ + protected function exportComments($comments) { + + $commentsArray = array(); + $exportComments = array(); + + foreach($comments as $comment) { + if($comment['status'] == Comment::statusSpam) continue; // don't export spam + $commentsArray[(int) $comment['id']] = $comment; // index by comment id + } + + foreach($commentsArray as $id => $comment) { + + $key = $this->getCommentExportKey($comment); + $comment = $this->exportComment($comment); + + if(!empty($comment['parent_id'])) { + $parentID = (int) $comment['parent_id']; + $parent = isset($commentsArray[$parentID]) ? $commentsArray[$parentID] : null; + if($parent) $comment['parent_key'] = $this->getCommentExportKey($parent); + } + + $exportComments[$key] = $comment; + } + + return $exportComments; + } + + /** + * Get key used for exporting a comment + * + * @param Comment|array $comment + * @return string + * + */ + protected function getCommentExportKey($comment) { + return "$comment[created] $comment[email]"; + } + + /** + * Export value + * + * @param Page $page + * @param Field $field + * @param array|int|object|string $value + * @param array $options + * @return array|string + * + */ + public function ___exportValue(Page $page, Field $field, $value, array $options = array()) { + $exportValue = array_values($this->exportComments($value)); + return $exportValue; + } + + /** + * Import value + * + * Note: importValue does not delete comments, only insert or update. + * + * @param Page $page + * @param Field $field + * @param array $value + * @param array $options + * @return array|string + * + */ + public function ___importValue(Page $page, Field $field, $value, array $options = array()) { + + $value = $this->exportComments($value); + $comments = $page->get($field->name); + $commentsArray = array(); + $addComments = array(); + $updateComments = array(); + $skipImportComments = array(); + $updatePropertyCounts = array(); + $skipUpdateProperties = array('id', 'created_user', 'created_users_id'); + + foreach($comments as $comment) { + if($comment->status == Comment::statusSpam) continue; + $key = $this->getCommentExportKey($comment); + $commentsArray[$key] = $comment; + } + + foreach($value as $key => $importCommentArray) { + + if(!empty($importCommentArray['parent_key'])) { + $parentKey = $importCommentArray['parent_key']; + if(!isset($commentsArray[$parentKey])) { + $skipImportComments[$key] = $importCommentArray; + continue; + } + $importCommentArray['parent_id'] = $commentsArray[$parentKey]['id']; + unset($importCommentArray['parent_key']); + } + + if(!isset($commentsArray[$key])) { + $addComments[$key] = $importCommentArray; + continue; + } + + $comment = $commentsArray[$key]; + $commentArray = $this->exportComment($comment); + + foreach($skipUpdateProperties as $property) { + unset($commentArray[$property]); + unset($importCommentArray[$property]); + } + + if($commentArray == $importCommentArray) continue; // no changes + + foreach($importCommentArray as $k => $v) { + if(isset($commentArray[$k]) && $commentArray[$k] == $v) continue; + $comment->set($k, $v); + $comment->quiet(true); + $updateComments[$key] = $comment; + if(!isset($updatePropertyCounts[$k])) $updatePropertyCounts[$k] = 0; + $updatePropertyCounts[$k]++; + } + } + + foreach($addComments as $commentArray) { + unset($commentArray['id']); + $u = $this->wire('users')->get("name=" . $this->wire('sanitizer')->pageName($commentArray['created_user'])); + $commentArray['created_users_id'] = $u->id; + unset($commentArray['created_user']); + $comment = $this->makeComment($page, $field, $commentArray); + $comment->quiet(true); + $comments->add($comment); + } + + $numAddComments = count($addComments); + $numUpdateComments = count($updateComments); + $numSkipComments = count($skipImportComments); + + if($numAddComments) { + $comments->message("$field->name: $numAddComments new added"); + } + + if($numUpdateComments) { + $counts = array(); + foreach($updatePropertyCounts as $property => $count) { + $counts[] = "$property ($count)"; + } + $comments->message("$field->name: $numUpdateComments updated - " . implode(', ', $counts)); + $comments->trackChange('value'); + } + + if($numSkipComments) { + $comments->warning( + "$field->name: $numSkipComments skipped because parent comment(s) not yet present (run import again)" + ); + } + + return $comments; + + } + }