diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index cd9800c93d..0e9f88212a --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ test/meta/Proto/ test/main/data/urls/parser.dump.new .idea test/onphp + +doc/doxy/spl-5.2.3 +doc/www/css +doc/www/pix + diff --git a/config.inc.php b/config.inc.php new file mode 100644 index 0000000000..ca2a8be12a --- /dev/null +++ b/config.inc.php @@ -0,0 +1,214 @@ + $second)) @@ -231,7 +226,7 @@ public static function isGreater($first, $second, $message = null) $message.', '.self::dumpOppositeArguments($first, $second) ); } - + public static function isLesserOrEqual($first, $second, $message = null) { if (!($first <= $second)) @@ -239,7 +234,7 @@ public static function isLesserOrEqual($first, $second, $message = null) $message.', '.self::dumpOppositeArguments($first, $second) ); } - + public static function isGreaterOrEqual($first, $second, $message = null) { if (!($first >= $second)) @@ -247,7 +242,7 @@ public static function isGreaterOrEqual($first, $second, $message = null) $message.', '.self::dumpOppositeArguments($first, $second) ); } - + public static function isInstance($first, $second, $message = null) { if (!ClassUtils::isInstanceOf($first, $second)) @@ -255,7 +250,7 @@ public static function isInstance($first, $second, $message = null) $message.', '.self::dumpOppositeArguments($first, $second) ); } - + public static function classExists($className, $message = null) { if (!class_exists($className, true)) @@ -263,7 +258,7 @@ public static function classExists($className, $message = null) $message.', class "'.$className.'" does not exists' ); } - + public static function methodExists($object, $method, $message = null) { if (!method_exists($object, $method)) @@ -276,7 +271,7 @@ public static function isUnreachable($message = 'unreachable code reached') { throw new WrongArgumentException($message); } - + /// exceptionless methods //@{ public static function checkInteger($value) @@ -287,7 +282,7 @@ public static function checkInteger($value) && (strlen($value) == strlen((int) $value)) ); } - + public static function checkFloat($value) { return ( @@ -300,12 +295,41 @@ public static function checkScalar($value) { return is_scalar($value); } - + + public static function checkBoolean($value) + { + return ($value === true || $value === false); + } + + /** + * Checking UUID + * @see http://tools.ietf.org/html/rfc4122 + * @param string $value + * @return boolean + */ + public static function checkUniversalUniqueIdentifier($value) + { + return ( + is_string($value) && + preg_match(PrimitiveUuidString::UUID_PATTERN, $value) + ); + } + + public static function isUniversalUniqueIdentifier($variable, $message = null) + { + if( + !self::checkUniversalUniqueIdentifier($variable) + ) + throw new WrongArgumentException( + $message.', '.self::dumpArgument($variable) + ); + } + public static function dumpArgument($argument) { return 'argument: ['.print_r($argument, true).']'; } - + public static function dumpOppositeArguments($first, $second) { return diff --git a/core/Base/Date.class.php b/core/Base/Date.class.php old mode 100644 new mode 100755 index 57ce906b7b..c0076cc269 --- a/core/Base/Date.class.php +++ b/core/Base/Date.class.php @@ -11,352 +11,340 @@ ***************************************************************************/ /* $Id$ */ +/** + * Date's container and utilities. + * + * @see DateRange + * + * @ingroup Base + **/ +class Date implements Stringable, DialectString +{ + const WEEKDAY_MONDAY = 1; + const WEEKDAY_TUESDAY = 2; + const WEEKDAY_WEDNESDAY = 3; + const WEEKDAY_THURSDAY = 4; + const WEEKDAY_FRIDAY = 5; + const WEEKDAY_SATURDAY = 6; + const WEEKDAY_SUNDAY = 0; // because strftime('%w') is 0 on Sunday + /** - * Date's container and utilities. - * - * @see DateRange - * - * @ingroup Base - **/ - class Date implements Stringable, DialectString + * @var DateTime + */ + protected $dateTime = null; + + // compatibility for serialized old objects + protected $int = null; + + /** + * @return Date + **/ + public static function create($date) { - const WEEKDAY_MONDAY = 1; - const WEEKDAY_TUESDAY = 2; - const WEEKDAY_WEDNESDAY = 3; - const WEEKDAY_THURSDAY = 4; - const WEEKDAY_FRIDAY = 5; - const WEEKDAY_SATURDAY = 6; - const WEEKDAY_SUNDAY = 0; // because strftime('%w') is 0 on Sunday - - protected $string = null; - protected $int = null; - - protected $year = null; - protected $month = null; - protected $day = null; - - /** - * @return Date - **/ - public static function create($date) - { - return new self($date); - } - - public static function today($delimiter = '-') - { - return date("Y{$delimiter}m{$delimiter}d"); - } - - /** - * @return Date - **/ - public static function makeToday() - { - return new self(self::today()); - } - - /** - * @return Date - * @see http://www.faqs.org/rfcs/rfc3339.html - * @see http://www.cl.cam.ac.uk/~mgk25/iso-time.html - **/ - public static function makeFromWeek($weekNumber, $year = null) - { - if (!$year) - $year = date('Y'); - - Assert::isTrue( - ($weekNumber > 0) - && ($weekNumber <= self::getWeekCountInYear($year)) - ); - - $date = - new self( - date( - self::getFormat(), - mktime( - 0, 0, 0, 1, 1, $year - ) + return new static($date); + } + + public static function today($delimiter = '-') + { + return date("Y{$delimiter}m{$delimiter}d"); + } + + /** + * @return Date + **/ + public static function makeToday() + { + return new static(static::today()); + } + + /** + * @return Date + * @see http://www.faqs.org/rfcs/rfc3339.html + * @see http://www.cl.cam.ac.uk/~mgk25/iso-time.html + **/ + public static function makeFromWeek($weekNumber, $year = null) + { + if (!$year) + $year = date('Y'); + + Assert::isTrue( + ($weekNumber > 0) + && ($weekNumber <= static::getWeekCountInYear($year)) + ); + + $date = + new static( + date( + static::getFormat(), + mktime( + 0, 0, 0, 1, 1, $year ) - ); - - $days = + ) + ); + + $days = + ( ( - ( - $weekNumber - 1 - + (self::getWeekCountInYear($year - 1) == 53 ? 1 : 0) - ) - * 7 - ) + 1 - $date->getWeekDay(); - - return $date->modify("+{$days} day"); - } - - public static function dayDifference(Date $left, Date $right) - { - return - gregoriantojd( - $right->getMonth(), - $right->getDay(), - $right->getYear() + $weekNumber - 1 + + (static::getWeekCountInYear($year - 1) == 53 ? 1 : 0) ) - - gregoriantojd( - $left->getMonth(), - $left->getDay(), - $left->getYear() - ); - } - - public static function compare(Date $left, Date $right) - { - if ($left->int == $right->int) - return 0; - else - return ($left->int > $right->int ? 1 : -1); - } + * 7 + ) + 1 - $date->getWeekDay(); - public static function getWeekCountInYear($year) - { - $weekCount = date('W', mktime(0, 0, 0, 12, 31, $year)); + return $date->modify("+{$days} day"); + } - if ($weekCount == '01') { - return date('W', mktime(0, 0, 0, 12, 24, $year)); - } else { - return $weekCount; - } - } + public static function dayDifference(Date $left, Date $right) + { + return + gregoriantojd( + $right->getMonth(), + $right->getDay(), + $right->getYear() + ) + - gregoriantojd( + $left->getMonth(), + $left->getDay(), + $left->getYear() + ); + } - public function __construct($date) - { - if (is_int($date) || is_numeric($date)) { // unix timestamp - $this->string = date($this->getFormat(), $date); - } elseif ($date && is_string($date)) - $this->stringImport($date); - - if ($this->string === null) { - throw new WrongArgumentException( - "strange input given - '{$date}'" - ); - } - - $this->import($this->string); - $this->buildInteger(); - } + public static function compare(Date $left, Date $right) + { + if ($left->toStamp() == $right->toStamp()) + return 0; + else + return ($left->toStamp() > $right->toStamp() ? 1 : -1); + } - public function __sleep() - { - return array('int'); - } + public static function getWeekCountInYear($year) + { + $weekCount = date('W', mktime(0, 0, 0, 12, 31, $year)); - public function __wakeup() - { - $this->import(date($this->getFormat(), $this->int)); - } - - public function toStamp() - { - return $this->int; - } - - public function toDate($delimiter = '-') - { - return - $this->year - .$delimiter - .$this->month - .$delimiter - .$this->day; - } - - public function getYear() - { - return $this->year; + if ($weekCount == '01') { + return date('W', mktime(0, 0, 0, 12, 24, $year)); + } else { + return $weekCount; } + } - public function getMonth() - { - return $this->month; - } + public function __construct($date) + { + $this->import($date); - public function getDay() - { - return $this->day; - } - - public function getWeek() - { - return date('W', $this->int); + if (! ($this->dateTime instanceof DateTime)) { + throw new WrongArgumentException( + "strange input given - '{$date}'" + ); } + } - public function getWeekDay() - { - return strftime('%w', $this->int); - } - - /** - * @return Date - **/ - public function spawn($modification = null) - { - $child = new $this($this->string); - - if ($modification) - return $child->modify($modification); - - return $child; + public function __clone() + { + $this->dateTime = clone $this->dateTime; + } + + public function __sleep() + { + $this->int = $this->dateTime->getTimestamp(); + return array('int'); + } + + public function __wakeup() { + $this->import($this->int); + } + + public function toStamp() + { + return $this->getDateTime()->getTimestamp(); + } + + public function toDate($delimiter = '-') + { + return + $this->getYear() + .$delimiter + .$this->getMonth() + .$delimiter + .$this->getDay(); + } + + public function getYear() + { + return $this->dateTime->format('Y'); + } + + public function getMonth() + { + return $this->dateTime->format('m'); + } + + public function getDay() + { + return $this->dateTime->format('d'); + } + + public function getWeek() + { + return date('W', $this->dateTime->getTimestamp()); + } + + public function getWeekDay() + { + return strftime('%w', $this->dateTime->getTimestamp()); + } + + /** + * @return Date + **/ + public function spawn($modification = null) + { + + $child = new static($this->toString()); + + if ($modification) + return $child->modify($modification); + + return $child; + } + + /** + * @throws WrongArgumentException + * @return Date + **/ + public function modify($string) + { + try { + $this->dateTime->modify($string); + } catch (Exception $e) { + throw new WrongArgumentException( + "wrong time string '{$string}'" + ); } - - /** - * @throws WrongArgumentException - * @return Date - **/ - public function modify($string) - { - try { - $time = strtotime($string, $this->int); - - if ($time === false) - throw new WrongArgumentException( - "modification yielded false '{$string}'" + + return $this; + } + + public function getDayStartStamp() + { + return + mktime( + 0, 0, 0, + $this->getMonth(), + $this->getDay(), + $this->getYear() + ); + } + + public function getDayEndStamp() + { + return + mktime( + 23, 59, 59, + $this->getMonth(), + $this->getDay(), + $this->getYear() + ); + } + + /** + * @return Date + **/ + public function getFirstDayOfWeek($weekStart = Date::WEEKDAY_MONDAY) + { + return $this->spawn( + '-'.((7 + $this->getWeekDay() - $weekStart) % 7).' days' + ); + } + + /** + * @return Date + **/ + public function getLastDayOfWeek($weekStart = Date::WEEKDAY_MONDAY) + { + return $this->spawn( + '+'.((13 - $this->getWeekDay() + $weekStart) % 7).' days' + ); + } + + public function toString() + { + return $this->dateTime->format(static::getFormat()); + } + + public function toFormatString($format) + { + return $this->dateTime->format($format); + } + + public function toDialectString(Dialect $dialect) + { + // there are no known differences yet + return $dialect->quoteValue($this->toString()); + } + + /** + * ISO 8601 date string + **/ + public function toIsoString() + { + return $this->toString(); + } + + /** + * @return Timestamp + **/ + public function toTimestamp() + { + return Timestamp::create($this->toStamp()); + } + + /** + * @return DateTime|null + */ + public function getDateTime() + { + return $this->dateTime; + } + + protected static function getFormat() + { + return 'Y-m-d'; + } + + + public function import($date) + { + try{ + if (is_int($date) || is_numeric($date)) { // unix timestamp + $this->dateTime = new DateTime(date(static::getFormat(), $date)); + + } elseif ($date && is_string($date)) { + + if ( + preg_match('/^(\d{1,4})[-\.](\d{1,2})[-\.](\d{1,2})/', $date, $matches) + ) { + Assert::isTrue( + checkdate($matches[2], $matches[3], $matches[1]) ); - - $this->int = $time; - $this->string = date($this->getFormat(), $time); - $this->import($this->string); - } catch (BaseException $e) { - throw new WrongArgumentException( - "wrong time string '{$string}'" - ); + } elseif ( + preg_match('/^(\d{1,2})[-\.](\d{1,2})[-\.](\d{1,4})/', $date, $matches) + ) { + Assert::isTrue( + checkdate($matches[2], $matches[1], $matches[3]) + ); + } + + $this->dateTime = new DateTime($date); } - - return $this; - } - - public function getDayStartStamp() - { - return - mktime( - 0, 0, 0, - $this->month, - $this->day, - $this->year - ); - } - - public function getDayEndStamp() - { - return - mktime( - 23, 59, 59, - $this->month, - $this->day, - $this->year - ); - } - - /** - * @return Date - **/ - public function getFirstDayOfWeek($weekStart = Date::WEEKDAY_MONDAY) - { - return $this->spawn( - '-'.((7 + $this->getWeekDay() - $weekStart) % 7).' days' - ); - } - - /** - * @return Date - **/ - public function getLastDayOfWeek($weekStart = Date::WEEKDAY_MONDAY) - { - return $this->spawn( - '+'.((13 - $this->getWeekDay() + $weekStart) % 7).' days' + + + } catch(Exception $e) { + throw new WrongArgumentException( + "strange input given - '{$date}'" ); } - - public function toString() - { - return $this->string; - } - - public function toFormatString($format) - { - return date($format, $this->toStamp()); - } - - public function toDialectString(Dialect $dialect) - { - // there are no known differences yet - return $dialect->quoteValue($this->toString()); - } - - /** - * ISO 8601 date string - **/ - public function toIsoString() - { - return $this->toString(); - } - - /** - * @return Timestamp - **/ - public function toTimestamp() - { - return Timestamp::create($this->toStamp()); - } - - protected static function getFormat() - { - return 'Y-m-d'; - } - - /* void */ protected function import($string) - { - list($this->year, $this->month, $this->day) = - explode('-', $string, 3); - - if (!$this->month || !$this->day) - throw new WrongArgumentException( - 'month and day must not be zero' - ); - - $this->string = - sprintf( - '%04d-%02d-%02d', - $this->year, - $this->month, - $this->day - ); - - list($this->year, $this->month, $this->day) = - explode('-', $this->string, 3); - } - - /* void */ protected function stringImport($string) - { - $matches = array(); - - if ( - preg_match('/^(\d{1,4})-(\d{1,2})-(\d{1,2})$/', $string, $matches) - ) { - if (checkdate($matches[2], $matches[3], $matches[1])) - $this->string = $string; - - } elseif (($stamp = strtotime($string)) !== false) - $this->string = date($this->getFormat(), $stamp); - } - - /* void */ protected function buildInteger() - { - $this->int = - mktime( - 0, 0, 0, - $this->month, - $this->day, - $this->year - ); - } + } -?> +} +?> \ No newline at end of file diff --git a/core/Base/Enum.class.php b/core/Base/Enum.class.php new file mode 100644 index 0000000000..8ed2cba698 --- /dev/null +++ b/core/Base/Enum.class.php @@ -0,0 +1,145 @@ +setInternalId($id); + } + + /** + * @param $id + * @return Enum + * @throws MissingElementException + */ + protected function setInternalId($id) + { + $names = static::getNameList(); + if (isset($names[$id])) { + $this->id = $id; + $this->name = $names[$id]; + } else + throw new MissingElementException( + get_class($this) . ' knows nothing about such id == '.$id + ); + + return $this; + } + + /** + * @return string + */ + public function serialize() + { + return (string) $this->id; + } + + /** + * @param $serialized + */ + public function unserialize($serialized) + { + $this->setInternalId($serialized); + } + + /** + * Array of object + * @static + * @return array + */ + public static function getList() + { + $list = array(); + foreach (array_keys(static::getNameList()) as $id) + $list[] = static::create($id); + + return $list; + } + + /** + * must return any existent ID + * 1 should be ok for most enumerations + * @return integer + **/ + public static function getAnyId() + { + return 1; + } + + /** + * @return null|integer + */ + public function getId() + { + return $this->id; + } + + + /** + * Alias for getList() + * @static + * @deprecated + * @return array + */ + public static function getObjectList() + { + return static::getList(); + } + + /** + * @return string + */ + public function toString() + { + return $this->name; + } + + /** + * Plain list + * @static + * @return array + */ + public static function getNameList() + { + return static::$names; + } + + /** + * @return Enum + **/ + public function setId($id) + { + throw new UnsupportedMethodException('You can not change id here, because it is politics for Enum!'); + } + } +?> \ No newline at end of file diff --git a/core/Base/Enumeration.class.php b/core/Base/Enumeration.class.php old mode 100644 new mode 100755 index b838c83a36..bdf4862849 --- a/core/Base/Enumeration.class.php +++ b/core/Base/Enumeration.class.php @@ -11,39 +11,83 @@ /** * Parent of all enumeration classes. - * + * * @see AccessMode for example - * + * * @ingroup Base * @ingroup Module **/ abstract class Enumeration extends NamedObject implements Serializable { protected $names = array(/* override me */); - - final public function __construct($id) + + public function __construct($id) { $this->setId($id); } - + + /// useful helper methods + //@{ + /** + * @param $id + * @return static + */ + public static function create($id) + { + return new static($id); + } + + /** + * @param int|Enumeration $enum + * @throws WrongArgumentException + * @return boolean + */ + public function is($enum) + { + if (is_scalar($enum)) { + $id = $enum; + } else if (is_object($enum) && is_a($enum, get_class($this))) { + $id = $enum->getId(); + } else { + throw new WrongArgumentException('cant match this enum with: ' . var_export($enum, true)); + } + return $id == $this->getId(); + } + + /** + * @param int[]|static[] $enums + * @return bool + * @throws WrongArgumentException + */ + public function in(array $enums) + { + foreach ($enums as $enum) { + if ($this->is($enum)) { + return true; + } + } + return false; + } + //@} + /// prevent's serialization of names' array //@{ public function serialize() { return (string) $this->id; } - + public function unserialize($serialized) { $this->setId($serialized); } //@} - + public static function getList(Enumeration $enum) { return $enum->getObjectList(); } - + /** * must return any existent ID * 1 should be ok for most enumerations @@ -52,20 +96,20 @@ public static function getAnyId() { return 1; } - + /// parent's getId() is too complex in our case public function getId() { return $this->id; } - + public function getObjectList() { $list = array(); $names = $this->getNameList(); - + foreach (array_keys($names) as $id) - $list[] = new $this($id); + $list[$id] = new $this($id); return $list; } @@ -74,12 +118,12 @@ public function toString() { return $this->name; } - + public function getNameList() { return $this->names; } - + /** * @return Enumeration **/ @@ -92,10 +136,28 @@ public function setId($id) $this->name = $names[$id]; } else throw new MissingElementException( - 'knows nothing about such id == '.$id + get_class($this) . ' knows nothing about such id == '.$id ); - + return $this; } + + /** + * @static + * @return static[] + */ + public static function makeObjectList() { + $enum = new static( static::getAnyId() ); + return $enum->getObjectList(); + } + + /** + * @static + * @return array + */ + public static function makeNameList() { + $enum = new static( static::getAnyId() ); + return $enum->getNameList(); + } } ?> \ No newline at end of file diff --git a/core/Base/Identifiable.class.php b/core/Base/Identifiable.class.php old mode 100644 new mode 100755 diff --git a/core/Base/IdentifiableObject.class.php b/core/Base/IdentifiableObject.class.php old mode 100644 new mode 100755 index cd3d055f61..1445fa4371 --- a/core/Base/IdentifiableObject.class.php +++ b/core/Base/IdentifiableObject.class.php @@ -17,7 +17,7 @@ * @ingroup Base * @ingroup Module **/ - class /* spirit of */ IdentifiableObject implements Identifiable, DialectString + class /* spirit of */ IdentifiableObject implements Identifiable, DialectString, ArrayAccess { protected $id = null; @@ -56,5 +56,25 @@ public function toDialectString(Dialect $dialect) { return $dialect->quoteValue($this->getId()); } + + public function offsetExists($offset) { + return isset($this->$offset); + } + + public function offsetGet($offset) { + return $this->{'get' . ucfirst($offset)}(); + } + + public function offsetSet($offset, $value) { + return $this->{'set' . ucfirst($offset)}($value); + } + + public function offsetUnset($offset) { + $dropper = 'drop' . ucfirst($offset); + if (method_exists($this, $dropper)) { + $this->{$dropper}(); + } + } + } ?> \ No newline at end of file diff --git a/core/Base/Identifier.class.php b/core/Base/Identifier.class.php old mode 100644 new mode 100755 index ae63db9ecb..f979eba40c --- a/core/Base/Identifier.class.php +++ b/core/Base/Identifier.class.php @@ -15,7 +15,7 @@ * @ingroup Base * @ingroup Module **/ - final class Identifier implements Identifiable + final class Identifier implements Identifiable, Stringable { private $id = null; private $final = false; @@ -65,5 +65,15 @@ public function isFinalized() { return $this->final; } + + public function toString() { + return $this->__toString(); + } + + function __toString() { + return strval($this->id); + } + + } ?> \ No newline at end of file diff --git a/core/Base/Instantiatable.class.php b/core/Base/Instantiatable.class.php old mode 100644 new mode 100755 diff --git a/core/Base/IntervalUnit.class.php b/core/Base/IntervalUnit.class.php old mode 100644 new mode 100755 diff --git a/core/Base/Listable.class.php b/core/Base/Listable.class.php new file mode 100644 index 0000000000..958486f772 --- /dev/null +++ b/core/Base/Listable.class.php @@ -0,0 +1,65 @@ + 'Unknown' + ); + + /** + * @param integer $id + * @return Registry + */ + public static function create($id) + { + return new static($id); + } + + public function __construct($id) + { + $this->setInternalId($id); + } + + /** + * @param $id + * @return Registry + * @throws MissingElementException + */ + protected function setInternalId($id) + { + if (isset(static::$names[$id])) { + $this->id = $id; + $this->name = static::$names[$id]; + } else + throw new MissingElementException( + get_class($this) . ' knows nothing about such id == '.$id + ); + + return $this; + } + + /** + * @return string + */ + public function serialize() + { + return (string) $this->id; + } + + /** + * @param $serialized + */ + public function unserialize($serialized) + { + $this->setInternalId($serialized); + } + + /** + * Array of object + * @static + * @return array + */ + public static function getList() + { + $list = array(); + foreach (array_keys(static::$names) as $id) + $list[] = static::create($id); + + return $list; + } + + /** + * must return any existent ID + * 1 should be ok for most enumerations + * @return integer + **/ + public static function getAnyId() + { + return static::NIL; + } + + /** + * @return null|integer + */ + public function getId() + { + return $this->id; + } + + + /** + * Alias for getList() + * @static + * @deprecated + * @return array + */ + public static function getObjectList() + { + return static::getList(); + } + + /** + * @return string + */ + public function toString() + { + return $this->name; + } + + /** + * Plain list + * @static + * @return array + */ + public static function getNameList() + { + return static::$names; + } + + /** + * @return Enum + **/ + public function setId($id) + { + throw new UnsupportedMethodException('You can not change id here, because it is politics for Registry!'); + } + } +?> \ No newline at end of file diff --git a/core/Base/Session.class.php b/core/Base/Session.class.php old mode 100644 new mode 100755 diff --git a/core/Base/Singleton.class.php b/core/Base/Singleton.class.php old mode 100644 new mode 100755 diff --git a/core/Base/StaticFactory.class.php b/core/Base/StaticFactory.class.php old mode 100644 new mode 100755 diff --git a/core/Base/Stringable.class.php b/core/Base/Stringable.class.php old mode 100644 new mode 100755 diff --git a/core/Base/Ternary.class.php b/core/Base/Ternary.class.php old mode 100644 new mode 100755 diff --git a/core/Base/Time.class.php b/core/Base/Time.class.php old mode 100644 new mode 100755 diff --git a/core/Base/Timestamp.class.php b/core/Base/Timestamp.class.php old mode 100644 new mode 100755 index f097d3db2a..5d0965b99e --- a/core/Base/Timestamp.class.php +++ b/core/Base/Timestamp.class.php @@ -11,197 +11,169 @@ ***************************************************************************/ /* $Id$ */ +/** + * Date and time container and utilities. + * + * @see Date + * + * @ingroup Base + **/ +class Timestamp extends Date +{ + protected $zone = null; + /** - * Date and time container and utilities. - * - * @see Date - * - * @ingroup Base - **/ - class Timestamp extends Date - { - private $hour = null; - private $minute = null; - private $second = null; - - /** - * @return Timestamp - **/ - public static function create($timestamp) - { - return new self($timestamp); - } - - public static function now() - { - return date(self::getFormat()); - } - - /** - * @return Timestamp - **/ - public static function makeNow() - { - return new self(time()); - } - - /** - * @return Timestamp - **/ - public static function makeToday() - { - return new self(self::today()); - } - - public function toTime($timeDelimiter = ':', $secondDelimiter = '.') - { - return - $this->hour - .$timeDelimiter - .$this->minute - .$secondDelimiter - .$this->second; - } - - public function toDateTime( - $dateDelimiter = '-', - $timeDelimiter = ':', - $secondDelimiter = '.' - ) - { - return - $this->toDate($dateDelimiter).' ' - .$this->toTime($timeDelimiter, $secondDelimiter); - } - - public function getHour() - { - return $this->hour; - } - - public function getMinute() - { - return $this->minute; - } - - public function getSecond() - { - return $this->second; - } - - public function equals(Timestamp $timestamp) - { - return ($this->toDateTime() === $timestamp->toDateTime()); - } - - public function getDayStartStamp() - { - if (!$this->hour && !$this->minute && !$this->second) - return $this->int; - else - return parent::getDayStartStamp(); - } + * @return Timestamp + **/ + public static function create($timestamp, DateTimeZone $zone=null) + { + return new static($timestamp, $zone); + } - public function getHourStartStamp() - { - if (!$this->minute && !$this->second) - return $this->int; - - return - mktime( - $this->hour, - 0, - 0, - $this->month, - $this->day, - $this->year - ); - } - - /** - * ISO 8601 time string - **/ - public function toIsoString($convertToUtc = true) - { - if ($convertToUtc) - return date('Y-m-d\TH:i:s\Z', $this->int - date('Z', $this->int)); - else - return date('Y-m-d\TH:i:sO', $this->int); - } - - /** - * @return Timestamp - **/ - public function toTimestamp() - { - return $this; - } - - protected static function getFormat() - { - return 'Y-m-d H:i:s'; - } - - /* void */ protected function import($string) - { - list($date, $time) = explode(' ', $string, 2); - - list($this->hour, $this->minute, $this->second) = - explode(':', $time, 3); - - $time = - sprintf( - '%02d:%02d:%02d', - $this->hour, - $this->minute, - $this->second - ); - - list($this->hour, $this->minute, $this->second) = - explode(':', $time, 3); - - parent::import($date); - - $this->string .= ' '.$time; + public static function now() + { + return date(static::getFormat()); + } + + /** + * @return Timestamp + **/ + public static function makeNow() + { + return new static(time()); + } + + /** + * @return Timestamp + **/ + public static function makeToday() + { + return new static(static::today()); + } + + public function __construct($dateTime, DateTimeZone $zone = null) + { + parent::__construct($dateTime); + + if (is_null($zone)) { + $zone = $this->getDefaultTimeZone(); } - - /* void */ protected function stringImport($string) - { - $matches = array(); - - if ( - preg_match( - '/^(\d{1,4})-(\d{1,2})-(\d{1,2})\s\d{1,2}:\d{1,2}:\d{1,2}$/', - $string, - $matches - ) - ) { - if (checkdate($matches[2], $matches[3], $matches[1])) - $this->string = $string; - } elseif ( - preg_match( - '/^(\d{1,4})-(\d{1,2})-(\d{1,2})$/', - $string, - $matches - ) - ) { - if (checkdate($matches[2], $matches[3], $matches[1])) - $this->string = $string . ' 00:00:00'; - } elseif (($stamp = strtotime($string)) !== false) - $this->string = date($this->getFormat(), $stamp); + + $this->dateTime->setTimezone($zone); + } + + public function __sleep() { + $this->zone = $this->dateTime->getTimezone()->getName(); + return array_merge(parent::__sleep(), array('zone')); + } + + public function __wakeup() { + parent::__wakeup(); + if ($this->zone) { + $this->dateTime->setTimezone(new DateTimeZone($this->zone)); } - - /* void */ protected function buildInteger() - { - $this->int = - mktime( - $this->hour, - $this->minute, - $this->second, - $this->month, - $this->day, - $this->year - ); + } + + private function getDefaultTimeZone() + { + $defaultTimeZoneName = date_default_timezone_get(); + try { + return new DateTimeZone($defaultTimeZoneName); + } catch(Exception $e) { + throw new WrongStateException( + "strange default time zone given - '{$defaultTimeZoneName}'!". + 'Use date_default_timezone_set() for set valid default time zone.' + ); } } + + public function toTime($timeDelimiter = ':', $secondDelimiter = '.') + { + return + $this->getHour() + .$timeDelimiter + .$this->getMinute() + .$secondDelimiter + .$this->getSecond(); + } + + public function toDateTime( + $dateDelimiter = '-', + $timeDelimiter = ':', + $secondDelimiter = '.' + ) + { + return + $this->toDate($dateDelimiter).' ' + .$this->toTime($timeDelimiter, $secondDelimiter); + } + + public function getHour() + { + return $this->dateTime->format('H'); + } + + public function getMinute() + { + return $this->dateTime->format('i'); + } + + public function getSecond() + { + return $this->dateTime->format('s'); + } + + public function equals(Timestamp $timestamp) + { + return ($this->toDateTime() === $timestamp->toDateTime()); + } + + public function getDayStartStamp() + { + if (!$this->getHour() && !$this->getMinute() && !$this->getSecond()) + return $this->dateTime->getTimestamp(); + else + return parent::getDayStartStamp(); + } + + public function getHourStartStamp() + { + if (!$this->getMinute() && !$this->getSecond()) + return $this->dateTime->getTimestamp(); + + return + mktime( + $this->getHour(), + 0, + 0, + $this->getMonth(), + $this->getDay(), + $this->getYear() + ); + } + + /** + * ISO 8601 time string + **/ + public function toIsoString($convertToUtc = true) + { + if ($convertToUtc) + return date('Y-m-d\TH:i:s\Z', $this->dateTime->getTimestamp() - date('Z', $this->dateTime->getTimestamp())); + else + return date('Y-m-d\TH:i:sO', $this->dateTime->getTimestamp()); + } + + /** + * @return Timestamp + **/ + public function toTimestamp() + { + return $this->spawn(); + } + + protected static function getFormat() + { + return 'Y-m-d H:i:s'; + } +} ?> \ No newline at end of file diff --git a/core/Base/TimestampTZ.class.php b/core/Base/TimestampTZ.class.php new file mode 100644 index 0000000000..dbab86c17a --- /dev/null +++ b/core/Base/TimestampTZ.class.php @@ -0,0 +1,57 @@ +toStamp(), $zone); + } + + return parent::toTimestamp(); + } + + public static function compare(Date $left, Date $right) + { + Assert::isTrue( + ( + $left instanceof TimestampTZ + && $right instanceof TimestampTZ + ) + ); + + return parent::compare($left, $right); + } +} +?> diff --git a/core/Base/Translatable.class.php b/core/Base/Translatable.class.php new file mode 100644 index 0000000000..c18826438b --- /dev/null +++ b/core/Base/Translatable.class.php @@ -0,0 +1,27 @@ +key = $key; + return $this; + } + + /** + * @return mixed + */ + public function getKey() { + return $this->key; + } +} \ No newline at end of file diff --git a/core/Cache/CacheListLink.class.php b/core/Cache/CacheListLink.class.php new file mode 100644 index 0000000000..d0a2168d3f --- /dev/null +++ b/core/Cache/CacheListLink.class.php @@ -0,0 +1,44 @@ +keys[$id] = $key; + return $this; + } + + /** + * @param mixed $keys + * @return $this + */ + public function setKeys($keys) { + $this->keys = $keys; + return $this; + } + + /** + * @return mixed + */ + public function getKeys() { + return $this->keys; + } +} \ No newline at end of file diff --git a/core/Cache/CachePeer.class.php b/core/Cache/CachePeer.class.php old mode 100644 new mode 100755 index 35ea662b27..dda09ecd7e --- a/core/Cache/CachePeer.class.php +++ b/core/Cache/CachePeer.class.php @@ -216,5 +216,17 @@ protected function restoreData($value) else return unserialize($value); } + + public function keys($pattern = null) { + return array(); + } + + public function deleteList(array $keys) { + return 0; + } + + public function deleteByPattern($pattern) { + return 0; + } } ?> \ No newline at end of file diff --git a/core/Cache/CyclicAggregateCache.class.php b/core/Cache/CyclicAggregateCache.class.php old mode 100644 new mode 100755 diff --git a/core/Cache/DebugCachePeer.class.php b/core/Cache/DebugCachePeer.class.php old mode 100644 new mode 100755 diff --git a/core/Cache/DirectoryLocker.class.php b/core/Cache/DirectoryLocker.class.php old mode 100644 new mode 100755 diff --git a/core/Cache/FileLocker.class.php b/core/Cache/FileLocker.class.php old mode 100644 new mode 100755 diff --git a/core/Cache/ListGenerator.class.php b/core/Cache/ListGenerator.class.php new file mode 100644 index 0000000000..b21a2a61b5 --- /dev/null +++ b/core/Cache/ListGenerator.class.php @@ -0,0 +1,21 @@ +alive = false; return null; } - + + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'memcached'))->begin(); $command = 'get '.implode(' ', $indexes)."\r\n"; if (!$this->sendRequest($command)) @@ -122,7 +124,12 @@ public function getList($indexes) // we can't deserialize objects inside parseGetRequest, // because of possibility further requests to memcached // during deserialization - in __wakeup(), for example - return unserialize($this->parseGetRequest(false)); + $list = unserialize($this->parseGetRequest(false)); + $profiling + ->setInfo($command) + ->end() + ; + return $list; } public function increment($key, $value) @@ -141,18 +148,27 @@ public function get($index) $this->alive = false; return null; } - + + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'memcached'))->begin(); $command = "get {$index}\r\n"; if (!$this->sendRequest($command)) return null; - - return $this->parseGetRequest(true); + + $result = $this->parseGetRequest(true); + $profiling + ->setInfo($command) + ->end() + ; + return $result; } public function delete($index, $time = null) { - $command = + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'memcached'))->begin(); + $command = $time ? "delete {$index} {$time}\r\n" : "delete {$index}\r\n"; @@ -161,7 +177,11 @@ public function delete($index, $time = null) return false; try { - $response = fread($this->link, $this->buffer); + $result = fread($this->link, $this->buffer); + $profiling + ->setInfo($command) + ->end() + ; } catch (BaseException $e) { return false; } @@ -169,7 +189,7 @@ public function delete($index, $time = null) if ($this->isTimeout()) return false; - if ($response === "DELETED\r\n") + if ($result === "DELETED\r\n") return true; else return false; @@ -177,6 +197,8 @@ public function delete($index, $time = null) public function append($key, $data) { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'memcached'))->begin(); $packed = serialize($data); $length = strlen($packed); @@ -187,12 +209,16 @@ public function append($key, $data) if (!$this->sendRequest($command)) return false; - $response = fread($this->link, $this->buffer); + $result = fread($this->link, $this->buffer); + $profiling + ->setInfo($command) + ->end() + ; if ($this->isTimeout()) return false; - if ($response === "STORED\r\n") + if ($result === "STORED\r\n") return true; return false; @@ -204,7 +230,9 @@ protected function store( { if ($expires === Cache::DO_NOT_CACHE) return false; - + + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'memcached'))->begin(); $flags = 0; if (!is_numeric($value)) { @@ -242,12 +270,16 @@ protected function store( if (!$this->sendRequest($command)) return false; - $response = fread($this->link, $this->buffer); + $result = fread($this->link, $this->buffer); + $profiling + ->setInfo($command) + ->end() + ; if ($this->isTimeout()) return false; - if ($response === "STORED\r\n") + if ($result === "STORED\r\n") return true; return false; @@ -328,14 +360,20 @@ private function changeInteger($command, $key, $value) { if (!$this->link) return null; - + + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'memcached'))->begin(); $command = "{$command} {$key} {$value}\r\n"; if (!$this->sendRequest($command)) return null; try { - $response = rtrim(fread($this->link, $this->buffer)); + $result = rtrim(fread($this->link, $this->buffer)); + $profiling + ->setInfo($command) + ->end() + ; } catch (BaseException $e) { return null; } @@ -343,8 +381,8 @@ private function changeInteger($command, $key, $value) if ($this->isTimeout()) return null; - if (is_numeric($response)) - return (int) $response; + if (is_numeric($result)) + return (int) $result; return null; } @@ -394,5 +432,85 @@ private function isTimeout() return $meta['timed_out']; } - } + + public function keys($pattern = null) { + if (!$this->link) { + $this->alive = false; + return null; + } + + if (is_string($pattern) && strlen($pattern)) { + $command = "keys {$pattern}\r\n"; + } else { + $command = "keys\r\n"; + } + + if (!$this->sendRequest($command)) + return null; + + $result = array(); + + while ($header = trim(fgets($this->link, $this->buffer))) { + $result[] = $header; + } + + if ($this->isTimeout()) + return null; + + return $result; + } + + public function deleteList(array $keys) { + + if (!$this->link) { + $this->alive = false; + return false; + } + + if (0 == count($keys)) + return false; + + $keys = implode(' ', $keys); + if (!$this->sendRequest("deletes {$keys}\r\n")) + return false; + + try { + $response = fread($this->link, $this->buffer); + } catch (BaseException $e) { + return false; + } + + if ($this->isTimeout()) + return false; + + return $response; + } + + public function deleteByPattern($pattern) { + + if (!$this->link) { + $this->alive = false; + return null; + } + + if (is_string($pattern) && strlen($pattern)) { + + if (!$this->sendRequest("delete_by_pattern {$pattern}\r\n")) + return false; + + try { + $response = fread($this->link, $this->buffer); + } catch (BaseException $e) { + return false; + } + + if ($this->isTimeout()) + return false; + + return $response; + } else { + return null; + } + } + } ?> diff --git a/core/Cache/MemcachedLocker.class.php b/core/Cache/MemcachedLocker.class.php old mode 100644 new mode 100755 diff --git a/core/Cache/PeclMemcached.class.php b/core/Cache/PeclMemcached.class.php old mode 100644 new mode 100755 diff --git a/core/Cache/ReadOnlyPeer.class.php b/core/Cache/ReadOnlyPeer.class.php old mode 100644 new mode 100755 diff --git a/core/Cache/RubberFileSystem.class.php b/core/Cache/RubberFileSystem.class.php old mode 100644 new mode 100755 diff --git a/core/Cache/RuntimeMemory.class.php b/core/Cache/RuntimeMemory.class.php old mode 100644 new mode 100755 diff --git a/core/Cache/SelectivePeer.class.php b/core/Cache/SelectivePeer.class.php old mode 100644 new mode 100755 diff --git a/core/Cache/SemaphorePool.class.php b/core/Cache/SemaphorePool.class.php old mode 100644 new mode 100755 diff --git a/core/Cache/SharedMemory.class.php b/core/Cache/SharedMemory.class.php old mode 100644 new mode 100755 diff --git a/core/Cache/SimpleAggregateCache.class.php b/core/Cache/SimpleAggregateCache.class.php old mode 100644 new mode 100755 diff --git a/core/Cache/SystemFiveLocker.class.php b/core/Cache/SystemFiveLocker.class.php old mode 100644 new mode 100755 diff --git a/core/Cache/WatermarkedPeer.class.php b/core/Cache/WatermarkedPeer.class.php old mode 100644 new mode 100755 index 3319db692d..4d1accd42d --- a/core/Cache/WatermarkedPeer.class.php +++ b/core/Cache/WatermarkedPeer.class.php @@ -168,5 +168,20 @@ protected function store( $this->getActualWatermark().$key, $value, $expires ); } + + public function keys($pattern = null) { + return $this->peer->keys($this->getActualWatermark() . '.*' . $pattern); + } + + public function deleteList(array $keys) { + foreach ($keys as $i => $key) { + $keys[$i] = $this->getActualWatermark() . $key; + } + return $this->peer->deleteList($keys); + } + + public function deleteByPattern($pattern) { + return $this->peer->deleteByPattern($this->getActualWatermark() . '.*' . $pattern); + } } ?> \ No newline at end of file diff --git a/core/Cache/eAcceleratorLocker.class.php b/core/Cache/eAcceleratorLocker.class.php old mode 100644 new mode 100755 diff --git a/core/DB/DB.class.php b/core/DB/DB.class.php old mode 100644 new mode 100755 index 3477408563..c0deaa4252 --- a/core/DB/DB.class.php +++ b/core/DB/DB.class.php @@ -35,6 +35,7 @@ abstract class DB * flag to indicate whether we're in transaction **/ private $transaction = false; + private $afterCommit = array(); private $queue = array(); private $toQueue = false; @@ -48,6 +49,7 @@ abstract public function queryRaw($queryString); abstract public function queryRow(Query $query); abstract public function querySet(Query $query); + abstract public function queryNumRows(Query $query); abstract public function queryColumn(Query $query); abstract public function queryCount(Query $query); @@ -64,7 +66,11 @@ public function __destruct() $this->disconnect(); } } - + + /** + * @return Dialect + * @throws UnimplementedFeatureException + */ public static function getDialect() { throw new UnimplementedFeatureException('implement me, please'); @@ -125,7 +131,11 @@ public function begin( $this->queryRaw("{$begin};\n"); $this->transaction = true; - + + if (!$this->toQueue) { + $this->afterCommit = array(); + } + return $this; } @@ -140,7 +150,11 @@ public function commit() $this->queryRaw("commit;\n"); $this->transaction = false; - + + if (!$this->toQueue) { + $this->onAfterCommit(); + } + return $this; } @@ -155,7 +169,11 @@ public function rollback() $this->queryRaw("rollback;\n"); $this->transaction = false; - + + if (!$this->toQueue) { + $this->afterCommit = array(); + } + return $this; } @@ -163,6 +181,23 @@ public function inTransaction() { return $this->transaction; } + + public function runAfterCommit($callback) { + $this->afterCommit[] = $callback; + if (!$this->inTransaction() && !$this->isQueueActive()) { + $this->onAfterCommit(); + } + return $this; + } + + private function onAfterCommit() { + foreach ($this->afterCommit as $callback) { + if (is_callable($callback)) { + call_user_func($callback); + } + } + $this->afterCommit = array(); + } //@} /** @@ -177,7 +212,9 @@ public function queueStart() { if ($this->hasQueue()) $this->toQueue = true; - + + $this->afterCommit = array(); + return $this; } @@ -187,7 +224,8 @@ public function queueStart() public function queueStop() { $this->toQueue = false; - + $this->afterCommit = array(); + return $this; } @@ -197,7 +235,8 @@ public function queueStop() public function queueDrop() { $this->queue = array(); - + $this->afterCommit = array(); + return $this; } @@ -212,6 +251,8 @@ public function queueFlush() ); $this->toQueue = false; + + $this->onAfterCommit(); return $this->queueDrop(); } diff --git a/core/DB/DBPool.class.php b/core/DB/DBPool.class.php old mode 100644 new mode 100755 index bb1b85a08a..56c38189c9 --- a/core/DB/DBPool.class.php +++ b/core/DB/DBPool.class.php @@ -71,7 +71,7 @@ public function addLink($name, DB $db) return $this; } - + /** * @throws MissingElementException * @return DBPool diff --git a/core/DB/Dialect.class.php b/core/DB/Dialect.class.php old mode 100644 new mode 100755 index 1323e24b9f..e2b6851128 --- a/core/DB/Dialect.class.php +++ b/core/DB/Dialect.class.php @@ -85,6 +85,9 @@ public function typeToString(DataType $type) if ($type->getId() == DataType::IP_RANGE) return 'varchar(41)'; + if ($type->getId() == DataType::UUID) + return 'varchar(36)'; + return $type->getName(); } @@ -159,5 +162,10 @@ public function quoteIpInRange($range, $ip) { throw new UnimplementedFeatureException(); } + + public function quoteArray($values, $type) + { + throw new UnimplementedFeatureException(); + } } ?> \ No newline at end of file diff --git a/core/DB/ImaginaryDialect.class.php b/core/DB/ImaginaryDialect.class.php old mode 100644 new mode 100755 diff --git a/core/DB/LiteDialect.class.php b/core/DB/LiteDialect.class.php old mode 100644 new mode 100755 diff --git a/core/DB/MyDialect.class.php b/core/DB/MyDialect.class.php old mode 100644 new mode 100755 diff --git a/core/DB/MyImprovedDialect.class.php b/core/DB/MyImprovedDialect.class.php old mode 100644 new mode 100755 diff --git a/core/DB/MySQL.class.php b/core/DB/MySQL.class.php old mode 100644 new mode 100755 index f69b8ca35d..db9dac840b --- a/core/DB/MySQL.class.php +++ b/core/DB/MySQL.class.php @@ -146,7 +146,16 @@ public function querySet(Query $query) } else return null; } - + + public function queryNumRows(Query $query) { + $res = $this->query($query); + + if ($res) { + return mysql_num_rows($res); + } else + return null; + } + public function queryRaw($queryString) { if (!$result = mysql_query($queryString, $this->link)) { diff --git a/core/DB/MySQLim.class.php b/core/DB/MySQLim.class.php old mode 100644 new mode 100755 index 5d9f78d493..d6c89cea30 --- a/core/DB/MySQLim.class.php +++ b/core/DB/MySQLim.class.php @@ -131,7 +131,16 @@ public function querySet(Query $query) } else return null; } - + + public function queryNumRows(Query $query) { + $res = $this->query($query); + + if ($res) { + return mysqli_num_rows($res); + } else + return null; + } + public function queryRaw($queryString) { if (!$result = mysqli_query($this->link, $queryString)) { diff --git a/core/DB/PgSQL.class.php b/core/DB/PgSQL.class.php old mode 100644 new mode 100755 index 7a450f7c2b..60764f6862 --- a/core/DB/PgSQL.class.php +++ b/core/DB/PgSQL.class.php @@ -25,7 +25,7 @@ public static function getDialect() { return PostgresDialect::me(); } - + /** * @return PgSQL **/ @@ -46,15 +46,15 @@ public function connect() throw new DatabaseException( 'can not connect to PostgreSQL server: '.pg_errormessage() ); - + if ($this->encoding) $this->setDbEncoding(); - + pg_set_error_verbosity($this->link, PGSQL_ERRORS_VERBOSE); return $this; } - + /** * @return PgSQL **/ @@ -65,54 +65,77 @@ public function disconnect() return $this; } - + public function isConnected() { return is_resource($this->link); } - + /** * misc **/ - + public function obtainSequence($sequence) { + $normalyzeSequence = mb_strtolower( trim( $sequence ) ); + if( + 'uuid' === $normalyzeSequence || + 'uuid_id' === $normalyzeSequence + ) { + return $this->obtainUuid(); + } + $res = $this->queryRaw("select nextval('{$sequence}') as seq"); $row = pg_fetch_assoc($res); pg_free_result($res); return $row['seq']; } - + + /** + * @return string + */ + protected function obtainUuid() + { + return UuidUtils::generate(); + } + /** * @return PgSQL **/ public function setDbEncoding() { pg_set_client_encoding($this->link, $this->encoding); - + return $this; } - + /** * query methods **/ - + public function queryRaw($queryString) { + $profiling = Profiling::create(array('db', get_class($this)), $queryString); try { - return pg_query($this->link, $queryString); + $profiling->begin(); + $result = pg_query($this->link, $queryString); + $profiling->end(); + return $result; + } catch (BaseException $e) { + $profiling->end(); // manual parsing, since pg_send_query() and // pg_get_result() is too slow in our case - list($error, ) = explode("\n", pg_errormessage($this->link)); - $code = substr($error, 8, 5); - +// list($error, ) = explode("\n", pg_errormessage($this->link)); + $error = str_replace(array("\n", "\r"), array(' ', ''), trim(pg_errormessage($this->link))); + sscanf($error, '%*s %[^:]', $code); + if ($code == PostgresError::UNIQUE_VIOLATION) { $e = 'DuplicateObjectException'; $code = null; } else $e = 'PostgresDatabaseException'; - + throw new $e($error.' - '.$queryString, $code); } } @@ -125,11 +148,11 @@ public function queryCount(Query $query) { return pg_affected_rows($this->queryNull($query)); } - + public function queryRow(Query $query) { $res = $this->query($query); - + if ($this->checkSingle($res)) { $ret = pg_fetch_assoc($res); pg_free_result($res); @@ -137,11 +160,11 @@ public function queryRow(Query $query) } else return null; } - + public function queryColumn(Query $query) { $res = $this->query($query); - + if ($res) { $array = array(); @@ -153,11 +176,11 @@ public function queryColumn(Query $query) } else return null; } - + public function querySet(Query $query) { $res = $this->query($query); - + if ($res) { $array = array(); @@ -169,12 +192,21 @@ public function querySet(Query $query) } else return null; } - + + public function queryNumRows(Query $query) { + $res = $this->query($query); + + if ($res) { + return pg_num_rows($res); + } else + return null; + } + public function hasSequences() { return true; } - + /** * @throws ObjectNotFoundException * @return DBTable @@ -185,35 +217,52 @@ public function getTableInfo($table) 'time' => DataType::TIME, 'date' => DataType::DATE, 'timestamp' => DataType::TIMESTAMP, - + + 'timestamptz' => DataType::TIMESTAMPTZ, + 'timestamp with time zone' => DataType::TIMESTAMPTZ, + + 'bool' => DataType::BOOLEAN, - + 'int2' => DataType::SMALLINT, 'int4' => DataType::INTEGER, 'int8' => DataType::BIGINT, 'numeric' => DataType::NUMERIC, - + 'float4' => DataType::REAL, 'float8' => DataType::DOUBLE, - + 'varchar' => DataType::VARCHAR, 'bpchar' => DataType::CHAR, 'text' => DataType::TEXT, - + 'bytea' => DataType::BINARY, - + 'ip4' => DataType::IP, 'inet' => DataType::IP, - + 'ip4r' => DataType::IP_RANGE, - + + 'cidr' => DataType::CIDR, + + 'uuid' => DataType::UUID, + 'hstore' => DataType::HSTORE, + + 'json' => DataType::JSON, + 'jsonb' => DataType::JSONB, + '_jsonb' => DataType::JSONB, + + '_varchar' => DataType::SET_OF_STRINGS, + '_int4' => DataType::SET_OF_INTEGERS, + '_int8' => DataType::SET_OF_INTEGERS, + '_float8' => DataType::SET_OF_FLOATS, + // unhandled types, not ours anyway 'tsvector' => null, - + 'ltree' => null, - 'hstore' => null, ); - + try { $res = pg_meta_data($this->link, $table); } catch (BaseException $e) { @@ -221,43 +270,66 @@ public function getTableInfo($table) "unknown table '{$table}'" ); } - + $table = new DBTable($table); - + foreach ($res as $name => $info) { - + Assert::isTrue( array_key_exists($info['type'], $types), - + 'unknown type "' .$types[$info['type']] .'" found in column "'.$name.'"' ); - + if (empty($types[$info['type']])) continue; - - $column = - new DBColumn( - DataType::create($types[$info['type']])-> - setNull(!$info['not null']), - - $name + + $type = DataType::create($types[$info['type']]) + ->setNull(!$info['not null']); + + if ($type->hasSize() || $type->hasPrecision()) { + $sizeInfo = $this->queryRow( + OSQL::select() + ->get('character_maximum_length') + ->get('numeric_precision') + ->get('numeric_scale') + ->from('information_schema.columns') + ->where(Expression::andBlock( + Expression::eq('table_schema', 'public'), + Expression::eq('table_catalog', $this->basename), + Expression::eq('table_name', $table->getName()), + Expression::eq('column_name', $name) + )) ); - + + if ($type->is(DataType::VARCHAR) || $type->is(DataType::CHAR)) { + if ($sizeInfo['character_maximum_length']) { + $type->setSize($sizeInfo['character_maximum_length']); + } + } + if ($type->is(DataType::NUMERIC)) { + $type->setSize($sizeInfo['numeric_precision']); + $type->setPrecision($sizeInfo['numeric_scale']); + } + } + + $column = new DBColumn($type, $name); + $table->addColumn($column); } - + return $table; } - + private function checkSingle($result) { if (pg_num_rows($result) > 1) throw new TooManyRowsException( 'query returned too many rows (we need only one)' ); - + return $result; } } diff --git a/core/DB/PostgresDialect.class.php b/core/DB/PostgresDialect.class.php old mode 100644 new mode 100755 index e11cb20713..9b9780a362 --- a/core/DB/PostgresDialect.class.php +++ b/core/DB/PostgresDialect.class.php @@ -16,11 +16,11 @@ * * @ingroup DB **/ - final class PostgresDialect extends Dialect + class PostgresDialect extends Dialect { private static $tsConfiguration = 'utf8_russian'; private static $rankFunction = 'rank'; - + /** * @return PostgresDialect **/ @@ -28,36 +28,36 @@ public static function me() { return Singleton::getInstance(__CLASS__); } - + public static function getTsConfiguration() { return self::$tsConfiguration; } - + public static function setTsConfiguration($configuration) { self::$tsConfiguration = $configuration; } - + public static function setRankFunction($rank) { self::$rankFunction = $rank; } - + public static function quoteValue($value) { return "'".pg_escape_string($value)."'"; } - + public static function toCasted($field, $type) { return "{$field}::{$type}"; } - + public static function prepareFullText(array $words, $logic) { $glue = ($logic == DB::FULL_TEXT_AND) ? ' & ' : ' | '; - + return mb_strtolower( implode( @@ -69,111 +69,142 @@ public static function prepareFullText(array $words, $logic) ) ); } - + public function quoteBinary($data) { - return "E'".pg_escape_bytea($data)."'"; + return "'".pg_escape_bytea($data)."'"; // deleted first E in qoutes; it was - "E'..." } - + public function unquoteBinary($data) { return pg_unescape_bytea($data); } - + public function typeToString(DataType $type) { if ($type->getId() == DataType::BINARY) return 'BYTEA'; - + if (defined('POSTGRES_IP4_ENABLED')) { - + if ($type->getId() == DataType::IP) return 'ip4'; - + if ($type->getId() == DataType::IP_RANGE) return 'ip4r'; } - + + if ($type->getId() == DataType::UUID) + return 'UUID'; + return parent::typeToString($type); } - + public function hasTruncate() { return true; } - + public function hasMultipleTruncate() { return true; } - + public function hasReturning() { return true; } - + public function fullTextSearch($field, $words, $logic) { $searchString = self::prepareFullText($words, $logic); $field = $this->fieldToString($field); - + return "({$field} @@ to_tsquery('".self::$tsConfiguration."', ". self::quoteValue($searchString)."))"; } - + public function fullTextRank($field, $words, $logic) { $searchString = self::prepareFullText($words, $logic); $field = $this->fieldToString($field); - + return self::$rankFunction."({$field}, to_tsquery('".self::$tsConfiguration."', ". self::quoteValue($searchString)."))"; } - + public function preAutoincrement(DBColumn $column) { self::checkColumn($column); - + return 'CREATE SEQUENCE "' .$this->makeSequenceName($column).'";'; } - + public function postAutoincrement(DBColumn $column) { self::checkColumn($column); - + return 'default nextval(\'' .$this->makeSequenceName($column).'\')'; } - + public function quoteIpInRange($range, $ip) { $string = ''; - + if ($ip instanceof DialectString) $string .= $ip->toDialectString($this); else $string .= $this->quoteValue($ip); - + $string .= ' <<= '; - + if ($range instanceof DialectString) $string .= $range->toDialectString($this); else $string .= $this->quoteValue($range); - - return $string; + + return $string; + } + + public function quoteArray($values, $type) + { + if (empty($values)) { + return self::LITERAL_NULL; + } + // add qoutes + foreach ($values as &$item) { + if ($type === DataType::INTEGER) { + $item = intval($item); + } else if ($type === DataType::REAL) { + $item = doubleval($item); + } elseif ($type === DataType::VARCHAR) { + $item = $this->quoteValue($item); + } else { + throw new WrongArgumentException('unknown type of array!'); + } + } + return 'ARRAY['.implode(', ',$values).']'; } + + public function quoteJson($values, $type) { + if( empty($values) ) { + return self::LITERAL_NULL; + } + return $this->quoteValue(json_encode($values)); + } + protected function makeSequenceName(DBColumn $column) { return $column->getTable()->getName().'_'.$column->getName(); } - + private static function checkColumn(DBColumn $column) { Assert::isTrue( @@ -181,5 +212,13 @@ private static function checkColumn(DBColumn $column) && ($column->getDefault() === null) ); } + + public static function quoteField($field) + { + if (is_string($field) && empty($field)) { + return "''"; + } + return self::quoteTable($field); + } } ?> \ No newline at end of file diff --git a/core/DB/PostgresError.class.php b/core/DB/PostgresError.class.php old mode 100644 new mode 100755 diff --git a/core/DB/Queue.class.php b/core/DB/Queue.class.php old mode 100644 new mode 100755 diff --git a/core/DB/SQLite.class.php b/core/DB/SQLite.class.php old mode 100644 new mode 100755 index def227b74c..28e2de2a28 --- a/core/DB/SQLite.class.php +++ b/core/DB/SQLite.class.php @@ -163,7 +163,16 @@ public function querySet(Query $query) } else return null; } - + + public function queryNumRows(Query $query) { + $res = $this->query($query); + + if ($res) { + return sqlite_num_rows($res); + } else + return null; + } + public function hasQueue() { return false; diff --git a/core/DB/Sequenceless.class.php b/core/DB/Sequenceless.class.php old mode 100644 new mode 100755 diff --git a/core/DB/Transaction/AccessMode.class.php b/core/DB/Transaction/AccessMode.class.php old mode 100644 new mode 100755 diff --git a/core/DB/Transaction/BaseTransaction.class.php b/core/DB/Transaction/BaseTransaction.class.php old mode 100644 new mode 100755 diff --git a/core/DB/Transaction/DBTransaction.class.php b/core/DB/Transaction/DBTransaction.class.php old mode 100644 new mode 100755 diff --git a/core/DB/Transaction/FakeTransaction.class.php b/core/DB/Transaction/FakeTransaction.class.php old mode 100644 new mode 100755 diff --git a/core/DB/Transaction/IsolationLevel.class.php b/core/DB/Transaction/IsolationLevel.class.php old mode 100644 new mode 100755 diff --git a/core/DB/Transaction/Transaction.class.php b/core/DB/Transaction/Transaction.class.php old mode 100644 new mode 100755 diff --git a/core/DB/Transaction/TransactionQueue.class.php b/core/DB/Transaction/TransactionQueue.class.php old mode 100644 new mode 100755 diff --git a/core/DB/Vertica.class.php b/core/DB/Vertica.class.php new file mode 100644 index 0000000000..ae2bafc8f0 --- /dev/null +++ b/core/DB/Vertica.class.php @@ -0,0 +1,110 @@ + + * @date 04.10.13 + */ + +class Vertica extends PgSQL { + + public static function getDialect() { + return VerticaDialect::me(); + } + + public function queryCount(Query $query) { + // that bitch pg_affected_rows returns 0 + return pg_num_rows($this->queryNull($query)); + } + + /** + * @throws ObjectNotFoundException + * @return DBTable + **/ + public function getTableInfo($table) + { + static $types = array( + 'time' => DataType::TIME, + 'date' => DataType::DATE, + 'timestamp' => DataType::TIMESTAMP, + 'timestamptz' => DataType::TIMESTAMPTZ, + 'timestamp with time zone' => DataType::TIMESTAMPTZ, + + 'boolean' => DataType::BOOLEAN, + + 'tinyint' => DataType::SMALLINT, + 'smallint' => DataType::SMALLINT, + 'integer' => DataType::INTEGER, + 'int' => DataType::INTEGER, + 'bigint' => DataType::BIGINT, + + 'numeric' => DataType::NUMERIC, + 'number' => DataType::NUMERIC, + + 'float' => DataType::DOUBLE, + + 'varchar' => DataType::VARCHAR, + 'char' => DataType::CHAR, + 'text' => DataType::TEXT, + + 'binary' => DataType::BINARY, + 'bytea' => DataType::BINARY, + 'varbinary' => DataType::BINARY, + 'raw' => DataType::BINARY, + + ); + + $query = OSQL::select() + ->from('columns') + ->arrayGet(array( + 'column_name', 'data_type', 'character_maximum_length', 'numeric_precision', 'numeric_scale', 'is_nullable' + )) + ->where(Expression::eq('table_name', $table)) + ; + + $res = $this->querySet($query); + + if (count($res) == 0) { + throw new ObjectNotFoundException( + "unknown table '{$table}'" + ); + } + + $table = new DBTable($table); + + foreach ($res as $info) { + $type = preg_replace('/[^a-z]/', '', $info['data_type']); + + Assert::isTrue( + array_key_exists($type, $types), + + 'unknown type "' + .$types[$type] + .'" found in column "'.$info['column_name'].'"' + ); + + if (empty($types[$type])) + continue; + + $dataType = + DataType::create($types[$type]) + ->setNull($info['is_nullable'] === 't'); + + if ($dataType->hasSize() && $info['character_maximum_length']) { + $dataType->setSize($info['character_maximum_length']); + } + if ($dataType->hasSize() && $info['numeric_precision']) { + $dataType->setSize($info['numeric_precision']); + } + if ($dataType->hasPrecision() && $info['numeric_scale']) { + $dataType->setPrecision($info['numeric_scale']); + } + + $table->addColumn(DBColumn::create( + $dataType, $info['column_name'] + )); + } + + return $table; + } + +} \ No newline at end of file diff --git a/core/DB/VerticaDialect.class.php b/core/DB/VerticaDialect.class.php new file mode 100644 index 0000000000..2718087a72 --- /dev/null +++ b/core/DB/VerticaDialect.class.php @@ -0,0 +1,15 @@ + + * @date 04.10.13 + */ + +class VerticaDialect extends PostgresDialect { + + /** @return self */ + public static function me() { + return Singleton::getInstance(__CLASS__); + } + +} \ No newline at end of file diff --git a/core/Exceptions/BadRequestException.class.php b/core/Exceptions/BadRequestException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/BaseException.class.php b/core/Exceptions/BaseException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/BusinessLogicException.class.php b/core/Exceptions/BusinessLogicException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/CachedObjectNotFoundException.class.php b/core/Exceptions/CachedObjectNotFoundException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/ClassNotFoundException.class.php b/core/Exceptions/ClassNotFoundException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/CtppException.class.php b/core/Exceptions/CtppException.class.php new file mode 100755 index 0000000000..61149478f0 --- /dev/null +++ b/core/Exceptions/CtppException.class.php @@ -0,0 +1,19 @@ + + * @date 2011.12.25 + */ +class CtppException extends BaseException {/*_*/} diff --git a/core/Exceptions/DatabaseException.class.php b/core/Exceptions/DatabaseException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/DuplicateObjectException.class.php b/core/Exceptions/DuplicateObjectException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/FileNotFoundException.class.php b/core/Exceptions/FileNotFoundException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/IOException.class.php b/core/Exceptions/IOException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/IOTimedOutException.class.php b/core/Exceptions/IOTimedOutException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/MissingElementException.class.php b/core/Exceptions/MissingElementException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/MissingModuleException.class.php b/core/Exceptions/MissingModuleException.class.php new file mode 100755 index 0000000000..5cd0d17252 --- /dev/null +++ b/core/Exceptions/MissingModuleException.class.php @@ -0,0 +1,19 @@ + + * @date 2011.12.25 + */ +class MissingModuleException extends BaseException {/*_*/} diff --git a/core/Exceptions/NetworkException.class.php b/core/Exceptions/NetworkException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/NoSQLException.class.php b/core/Exceptions/NoSQLException.class.php new file mode 100755 index 0000000000..082bdf8a50 --- /dev/null +++ b/core/Exceptions/NoSQLException.class.php @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/core/Exceptions/ObjectNotFoundException.class.php b/core/Exceptions/ObjectNotFoundException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/PostgresDatabaseException.class.php b/core/Exceptions/PostgresDatabaseException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/SecurityException.class.php b/core/Exceptions/SecurityException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/SoapFault.class.php b/core/Exceptions/SoapFault.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/SyntaxErrorException.class.php b/core/Exceptions/SyntaxErrorException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/TooManyRowsException.class.php b/core/Exceptions/TooManyRowsException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/UnimplementedFeatureException.class.php b/core/Exceptions/UnimplementedFeatureException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/UnsupportedMethodException.class.php b/core/Exceptions/UnsupportedMethodException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/WrongArgumentException.class.php b/core/Exceptions/WrongArgumentException.class.php old mode 100644 new mode 100755 diff --git a/core/Exceptions/WrongStateException.class.php b/core/Exceptions/WrongStateException.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filter.class.php b/core/Form/Filter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/BaseFilter.class.php b/core/Form/Filters/BaseFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/CallbackFilter.class.php b/core/Form/Filters/CallbackFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/CompressWhitespaceFilter.class.php b/core/Form/Filters/CompressWhitespaceFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/CropFilter.class.php b/core/Form/Filters/CropFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/DateRangeDisplayFilter.class.php b/core/Form/Filters/DateRangeDisplayFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/FilterChain.class.php b/core/Form/Filters/FilterChain.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/Filtrator.class.php b/core/Form/Filters/Filtrator.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/HashFilter.class.php b/core/Form/Filters/HashFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/HtmlSpecialCharsFilter.class.php b/core/Form/Filters/HtmlSpecialCharsFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/JsonDecoderFilter.class.php b/core/Form/Filters/JsonDecoderFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/JsonEncoderFilter.class.php b/core/Form/Filters/JsonEncoderFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/LowerCaseFilter.class.php b/core/Form/Filters/LowerCaseFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/NewLinesToBreaks.class.php b/core/Form/Filters/NewLinesToBreaks.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/NormalizeUrlFilter.class.php b/core/Form/Filters/NormalizeUrlFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/PCREFilter.class.php b/core/Form/Filters/PCREFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/Paragraphizer.class.php b/core/Form/Filters/Paragraphizer.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/RemoveNewlineFilter.class.php b/core/Form/Filters/RemoveNewlineFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/RussianTypograph.class.php b/core/Form/Filters/RussianTypograph.class.php old mode 100644 new mode 100755 index 6d96d692fd..31d59769e3 --- a/core/Form/Filters/RussianTypograph.class.php +++ b/core/Form/Filters/RussianTypograph.class.php @@ -63,7 +63,7 @@ final class RussianTypograph extends BaseFilter '~(\s)\s*~u', // n -> 2 whitespaces to process short strings (bar to a foo) '~([\s\pP]|^)([\w\pL]{1,2})\s~Uu', // bar a foo | bar to a foo '~( |\s)\s+~u', // compress whitespaces - '~\"(.*)\"~e', // "qu"o"te" + '~\"(.*?)\"~e', // "qu"o"te" '~\"([^\s]*)\"~', // "quote" '~\"([^\s]*)\s+([^\s\.]*)\"~', // "quote quote" '~([\w\pL\']+)~eu' // rock'n'roll diff --git a/core/Form/Filters/SafeUtf8Filter.class.php b/core/Form/Filters/SafeUtf8Filter.class.php old mode 100644 new mode 100755 index 24363b8283..f2875b3e6e --- a/core/Form/Filters/SafeUtf8Filter.class.php +++ b/core/Form/Filters/SafeUtf8Filter.class.php @@ -28,7 +28,7 @@ public function apply($value) // voodoo magic from w3 validator preg_match_all( - '/[\x00-\x7F] ' # ASCII + '/[\x0A\x0D\x20-\x7F] ' # ASCII .'| [\xC2-\xDF] [\x80-\xBF] ' # non-overlong 2-byte sequences .'| \xE0[\xA0-\xBF] [\x80-\xBF] ' # excluding overlongs .'| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} ' # straight 3-byte sequences diff --git a/core/Form/Filters/StringReplaceFilter.class.php b/core/Form/Filters/StringReplaceFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/StripTagsFilter.class.php b/core/Form/Filters/StripTagsFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/TrimFilter.class.php b/core/Form/Filters/TrimFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/UnTypographizer.class.php b/core/Form/Filters/UnTypographizer.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/UnixToUnixDecode.class.php b/core/Form/Filters/UnixToUnixDecode.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/UnixToUnixEncode.class.php b/core/Form/Filters/UnixToUnixEncode.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/UnserializeFilter.class.php b/core/Form/Filters/UnserializeFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/UpperCaseFilter.class.php b/core/Form/Filters/UpperCaseFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/UrlDecodeFilter.class.php b/core/Form/Filters/UrlDecodeFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/UrlEncodeFilter.class.php b/core/Form/Filters/UrlEncodeFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/Utf16ConverterFilter.class.php b/core/Form/Filters/Utf16ConverterFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Filters/WordSplitterFilter.class.php b/core/Form/Filters/WordSplitterFilter.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Form.class.php b/core/Form/Form.class.php old mode 100644 new mode 100755 index b4c9077823..7b72ff7595 --- a/core/Form/Form.class.php +++ b/core/Form/Form.class.php @@ -11,25 +11,25 @@ /** * Complete Form class. - * + * * @ingroup Form * @ingroup Module - * + * * @see http://onphp.org/examples.Form.en.html **/ final class Form extends RegulatedForm { const WRONG = 0x0001; const MISSING = 0x0002; - + private $errors = array(); private $labels = array(); private $describedLabels = array(); - + private $proto = null; - + private $importFiltering = true; - + /** * @return Form **/ @@ -37,18 +37,25 @@ public static function create() { return new self; } - + public function getErrors() { return array_merge($this->errors, $this->violated); } + + public function check() { + if ($this->getErrors()) { + throw new FormValidationException($this); + } + return $this; + } public function hasError($name) { return array_key_exists($name, $this->errors) || array_key_exists($name, $this->violated); } - + public function getError($name) { if (array_key_exists($name, $this->errors)) { @@ -58,11 +65,11 @@ public function getError($name) } return null; } - + public function getInnerErrors() { $result = $this->getErrors(); - + foreach ($this->primitives as $name => $prm) { if ( ( @@ -78,10 +85,10 @@ public function getInnerErrors() } } } - + return $result; } - + /** * @return Form **/ @@ -89,30 +96,30 @@ public function dropAllErrors() { $this->errors = array(); $this->violated = array(); - + return $this; } - + /** * @return Form **/ public function enableImportFiltering() { $this->importFiltering = true; - + return $this; } - + /** * @return Form **/ public function disableImportFiltering() { $this->importFiltering = false; - + return $this; } - + /** * primitive marking **/ @@ -124,10 +131,10 @@ public function markMissing($primitiveName) { return $this->markCustom($primitiveName, Form::MISSING); } - + /** * rule or primitive - * + * * @return Form **/ public function markWrong($name) @@ -140,10 +147,10 @@ public function markWrong($name) throw new MissingElementException( $name.' does not match known primitives or rules' ); - + return $this; } - + /** * @return Form **/ @@ -157,40 +164,44 @@ public function markGood($primitiveName) throw new MissingElementException( $primitiveName.' does not match known primitives or rules' ); - + return $this; } - + /** * Set's custom error mark for primitive. - * + * * @return Form **/ public function markCustom($primitiveName, $customMark) { Assert::isInteger($customMark); - + $this->errors[$this->get($primitiveName)->getName()] = $customMark; - + return $this; } //@} - + /** * Returns plain list of error's labels **/ - public function getTextualErrors() - { + public function getTextualErrors($withName = false) { $list = array(); - + foreach (array_keys($this->labels) as $name) { - if ($label = $this->getTextualErrorFor($name)) - $list[] = $label; + if ($label = $this->getTextualErrorFor($name)) { + if ($withName) { + $list[$name] = $label; + } else { + $list[] = $label; + } + } } - + return $list; } - + public function getTextualErrorFor($name) { if ( @@ -210,7 +221,7 @@ public function getTextualErrorFor($name) else return null; } - + public function getErrorDescriptionFor($name) { if ( @@ -230,13 +241,13 @@ public function getErrorDescriptionFor($name) else return null; } - + /** * @return Form **/ public function addErrorDescription($name, $errorType, $description) { - + if ( !isset($this->rules[$name]) && !$this->get($name)->getName() @@ -244,12 +255,12 @@ public function addErrorDescription($name, $errorType, $description) throw new MissingElementException( "knows nothing about '{$name}'" ); - + $this->describedLabels[$name][$errorType] = $description; - + return $this; } - + /** * @return Form **/ @@ -257,7 +268,7 @@ public function addWrongLabel($primitiveName, $label) { return $this->addErrorLabel($primitiveName, Form::WRONG, $label); } - + /** * @return Form **/ @@ -265,7 +276,7 @@ public function addMissingLabel($primitiveName, $label) { return $this->addErrorLabel($primitiveName, Form::MISSING, $label); } - + /** * @return Form **/ @@ -273,17 +284,17 @@ public function addCustomLabel($primitiveName, $customMark, $label) { return $this->addErrorLabel($primitiveName, $customMark, $label); } - + public function getWrongLabel($primitiveName) { return $this->getErrorLabel($primitiveName, Form::WRONG); } - + public function getMissingLabel($primitiveName) { return $this->getErrorLabel($primitiveName, Form::MISSING); } - + /** * @return Form **/ @@ -291,10 +302,10 @@ public function import($scope) { foreach ($this->primitives as $prm) $this->importPrimitive($scope, $prm); - + return $this; } - + /** * @return Form **/ @@ -304,10 +315,10 @@ public function importMore($scope) if (!$prm->isImported()) $this->importPrimitive($scope, $prm); } - + return $this; } - + /** * @return Form **/ @@ -315,67 +326,69 @@ public function importOne($primitiveName, $scope) { return $this->importPrimitive($scope, $this->get($primitiveName)); } - + /** * @return Form **/ public function importValue($primitiveName, $value) { $prm = $this->get($primitiveName); - + return $this->checkImportResult($prm, $prm->importValue($value)); } - + /** * @return Form **/ public function importOneMore($primitiveName, $scope) { $prm = $this->get($primitiveName); - + if (!$prm->isImported()) return $this->importPrimitive($scope, $prm); - + return $this; } - + public function exportValue($primitiveName) { return $this->get($primitiveName)->exportValue(); } - + public function export() { $result = array(); - + foreach ($this->primitives as $name => $prm) { if ($prm->isImported()) $result[$name] = $prm->exportValue(); } - + return $result; } - + public function toFormValue($value) { - if ($value instanceof FormField) + if( $value instanceof MappedFormField ) + return $value->toValue($this); + elseif ($value instanceof FormField) return $this->getValue($value->getName()); elseif ($value instanceof LogicalObject) return $value->toBoolean($this); else return $value; } - + /** * @return Form **/ public function setProto(EntityProto $proto) { $this->proto = $proto; - + return $this; } - + /** * @return EntityProto **/ @@ -383,7 +396,7 @@ public function getProto() { return $this->proto; } - + /** * @return Form **/ @@ -391,20 +404,20 @@ private function importPrimitive($scope, BasePrimitive $prm) { if (!$this->importFiltering) { if ($prm instanceof FiltrablePrimitive) { - + $chain = $prm->getImportFilter(); - + $prm->dropImportFilters(); - + $result = $this->checkImportResult( $prm, $prm->import($scope) ); - + $prm->setImportFilter($chain); - + return $result; - + } elseif ($prm instanceof PrimitiveForm) { return $this->checkImportResult( $prm, @@ -412,10 +425,10 @@ private function importPrimitive($scope, BasePrimitive $prm) ); } } - + return $this->checkImportResult($prm, $prm->import($scope)); } - + /** * @return Form **/ @@ -426,30 +439,30 @@ private function checkImportResult(BasePrimitive $prm, $result) && $result !== null ) $this->markGood($prm->getInner()->getName()); - + $name = $prm->getName(); - + if (null === $result) { if ($prm->isRequired()) $this->errors[$name] = self::MISSING; - + } elseif (true === $result) { unset($this->errors[$name]); - + } elseif ($error = $prm->getCustomError()) { - + $this->errors[$name] = $error; - + } else $this->errors[$name] = self::WRONG; - + return $this; } - + /** * Assigns specific label for given primitive and error type. * One more example of horrible documentation style. - * + * * @param $name string primitive or rule name * @param $errorType enum Form::(WRONG|MISSING) * @param $label string YDFB WTF is this :-) (c) /. @@ -465,20 +478,20 @@ private function addErrorLabel($name, $errorType, $label) throw new MissingElementException( "knows nothing about '{$name}'" ); - + $this->labels[$name][$errorType] = $label; - + return $this; } - + private function getErrorLabel($name, $errorType) { // checks for primitive's existence $this->get($name); - + if (isset($this->labels[$name][$errorType])) return $this->labels[$name][$errorType]; - + return null; } } diff --git a/core/Form/FormField.class.php b/core/Form/FormField.class.php old mode 100644 new mode 100755 index f472d2c6ce..97e59eb865 --- a/core/Form/FormField.class.php +++ b/core/Form/FormField.class.php @@ -11,20 +11,20 @@ /** * Atom for using in LogicalExpression. - * + * * @see DBField - * + * * @ingroup Form **/ - final class FormField + class FormField { private $primitiveName = null; - + public function __construct($name) { $this->primitiveName = $name; } - + /** * @return FormField **/ @@ -37,7 +37,7 @@ public function getName() { return $this->primitiveName; } - + public function toValue(Form $form) { return $form->getValue($this->primitiveName); diff --git a/core/Form/FormUtils.class.php b/core/Form/FormUtils.class.php old mode 100644 new mode 100755 index d877b6dcdc..4a691f1e60 --- a/core/Form/FormUtils.class.php +++ b/core/Form/FormUtils.class.php @@ -26,7 +26,7 @@ final class FormUtils extends StaticFactory $proto = $object->proto(); foreach (array_keys($proto->getExpandedPropertyList()) as $name) { - if ($form->primitiveExists($name)) { + if ($form->exists($name)) { $proto->importPrimitive( $name, $form, diff --git a/core/Form/MappedForm.class.php b/core/Form/MappedForm.class.php old mode 100644 new mode 100755 diff --git a/core/Form/MappedFormField.class.php b/core/Form/MappedFormField.class.php new file mode 100755 index 0000000000..636e01b206 --- /dev/null +++ b/core/Form/MappedFormField.class.php @@ -0,0 +1,156 @@ +map = $value; + + return $this; + } + + /** + * @return string + */ + public function getMap() + { + return $this->map; + } + + /** + * @param bool $isSilent + * @return MappedFormField + */ + public function setSilent($isSilent) + { + $this->isSilent = $isSilent; + return $this; + } + + /** + * @return bool + */ + public function getIsSilent() + { + return $this->isSilent; + } + + /** + * @return array + */ + protected function makeMapChain() + { + Assert::isNotNull( + $this->getMap(), + __METHOD__.': '. + _('you must be set "method"!') + ); + + $chain = array(); + $map = $this->getMap(); + $delimiter = '.'; + + if( mb_strstr($map, $delimiter) !== FALSE ) + { + $chain = explode($delimiter, $map); + } else { + $chain[] = $map; + } + + return $chain; + } + + public function toValue(Form $form) + { + $object = parent::toValue($form); + + if( $object === null ) + return null; + + + Assert::isInstance( + $object, + 'Prototyped', + __METHOD__.': '. + _('value must be instance of Prototyped!') + ); + + $result = $object; + $mapChain = $this->makeMapChain(); + foreach ( $mapChain as $propertyName ) + { + if( + !is_object( $result ) + ) { + if( $this->isSilent ) { + return NULL; + } else { + throw new WrongArgumentException( + __METHOD__.': '. + _('previous property not is a object type!') + ); + } + } + + if( + is_object($result) && + !($result instanceof Prototyped) + ) { + if( $this->isSilent ) { + return NULL; + } else { + throw new WrongArgumentException( + __METHOD__.': '. + _('result must be instance of Prototyped!') + ); + } + } + + + $property = $result->proto()->getPropertyByName( $propertyName ); + $result = call_user_func( + array( + $result, + $property->getGetter() + ) + ); + + } + + return $result; + } + + } diff --git a/core/Form/PlainForm.class.php b/core/Form/PlainForm.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitive.class.php b/core/Form/Primitive.class.php old mode 100644 new mode 100755 index beb3d364ad..a4228b028f --- a/core/Form/Primitive.class.php +++ b/core/Form/Primitive.class.php @@ -22,10 +22,10 @@ final class Primitive extends StaticFactory public static function spawn($primitive, $name) { Assert::classExists($primitive); - + return new $primitive($name); } - + /** * @return Primitive **/ @@ -33,7 +33,7 @@ public static function alias($name, BasePrimitive $prm) { return new PrimitiveAlias($name, $prm); } - + /** * @return PrimitiveAnyType **/ @@ -41,7 +41,7 @@ public static function anyType($name) { return new PrimitiveAnyType($name); } - + /** * @return PrimitiveInteger **/ @@ -49,7 +49,7 @@ public static function integer($name) { return new PrimitiveInteger($name); } - + /** * @return PrimitiveFloat **/ @@ -57,7 +57,7 @@ public static function float($name) { return new PrimitiveFloat($name); } - + /** * @return PrimitiveIdentifier * @obsoleted by integerIdentifier and scalarIdentifier @@ -66,7 +66,7 @@ public static function identifier($name) { return new PrimitiveIdentifier($name); } - + /** * @return PrimitiveIntegerIdentifier **/ @@ -74,7 +74,7 @@ public static function integerIdentifier($name) { return new PrimitiveIntegerIdentifier($name); } - + /** * @return PrimitiveScalarIdentifier **/ @@ -82,7 +82,7 @@ public static function scalarIdentifier($name) { return new PrimitiveScalarIdentifier($name); } - + /** * @return PrimitivePolymorphicIdentifier **/ @@ -90,7 +90,7 @@ public static function polymorphicIdentifier($name) { return new PrimitivePolymorphicIdentifier($name); } - + /** * @return PrimitiveIdentifierList **/ @@ -98,7 +98,7 @@ public static function identifierlist($name) { return new PrimitiveIdentifierList($name); } - + /** * @return PrimitiveClass **/ @@ -106,7 +106,7 @@ public static function clazz($name) { return new PrimitiveClass($name); } - + /** * @return PrimitiveEnumeration **/ @@ -114,7 +114,7 @@ public static function enumeration($name) { return new PrimitiveEnumeration($name); } - + /** * @return PrimitiveEnumerationByValue **/ @@ -122,7 +122,7 @@ public static function enumerationByValue($name) { return new PrimitiveEnumerationByValue($name); } - + /** * @return PrimitiveEnumerationList **/ @@ -130,7 +130,7 @@ public static function enumerationList($name) { return new PrimitiveEnumerationList($name); } - + /** * @return PrimitiveDate **/ @@ -138,7 +138,7 @@ public static function date($name) { return new PrimitiveDate($name); } - + /** * @return PrimitiveTimestamp **/ @@ -146,7 +146,15 @@ public static function timestamp($name) { return new PrimitiveTimestamp($name); } - + + /** + * @return PrimitiveTimestampTZ + **/ + public static function timestampTZ($name) + { + return new PrimitiveTimestampTZ($name); + } + /** * @return PrimitiveTime **/ @@ -154,7 +162,7 @@ public static function time($name) { return new PrimitiveTime($name); } - + /** * @return PrimitiveString **/ @@ -162,7 +170,7 @@ public static function string($name) { return new PrimitiveString($name); } - + /** * @return PrimitiveBinary **/ @@ -170,7 +178,7 @@ public static function binary($name) { return new PrimitiveBinary($name); } - + /** * @return PrimitiveRange **/ @@ -178,7 +186,7 @@ public static function range($name) { return new PrimitiveRange($name); } - + /** * @return PrimitiveDateRange **/ @@ -186,7 +194,7 @@ public static function dateRange($name) { return new PrimitiveDateRange($name); } - + /** * @return PrimitiveTimestampRange **/ @@ -194,7 +202,7 @@ public static function timestampRange($name) { return new PrimitiveTimestampRange($name); } - + /** * @return PrimitiveList **/ @@ -202,7 +210,7 @@ public static function choice($name) { return new PrimitiveList($name); } - + /** * @return PrimitiveArray **/ @@ -210,7 +218,31 @@ public static function set($name) { return new PrimitiveArray($name); } - + + /** + * @return PrimitiveArray + **/ + public static function arrayOfStrings($name) + { + return new PrimitiveArray($name); + } + + /** + * @return PrimitiveArray + **/ + public static function arrayOfIntegers($name) + { + return new PrimitiveArray($name); + } + + /** + * @return PrimitiveArray + **/ + public static function arrayOfFloats($name) + { + return new PrimitiveArray($name); + } + /** * @return PrimitiveHstore **/ @@ -218,7 +250,23 @@ public static function hstore($name) { return new PrimitiveHstore($name); } - + + /** + * @param $name + * @return PrimitiveArray + */ + public static function json($name) { + return new PrimitiveJson($name); + } + + /** + * @param $name + * @return PrimitiveArray + */ + public static function jsonb($name) { + return new PrimitiveJson($name); + } + /** * @return PrimitiveMultiList **/ @@ -226,7 +274,7 @@ public static function multiChoice($name) { return new PrimitiveMultiList($name); } - + /** * @return PrimitivePlainList **/ @@ -234,7 +282,7 @@ public static function plainChoice($name) { return new PrimitivePlainList($name); } - + /** * @return PrimitiveBoolean **/ @@ -242,7 +290,7 @@ public static function boolean($name) { return new PrimitiveBoolean($name); } - + /** * @return PrimitiveTernary **/ @@ -250,7 +298,7 @@ public static function ternary($name) { return new PrimitiveTernary($name); } - + /** * @return PrimitiveFile **/ @@ -258,7 +306,15 @@ public static function file($name) { return new PrimitiveFile($name); } - + + /** + * @return PrimitiveFile + **/ + public static function arrayOfFiles($name) + { + return new PrimitiveArrayOfFiles($name); + } + /** * @return PrimitiveImage **/ @@ -266,7 +322,7 @@ public static function image($name) { return new PrimitiveImage($name); } - + /** * @return ExplodedPrimitive **/ @@ -274,7 +330,7 @@ public static function exploded($name) { return new ExplodedPrimitive($name); } - + /** * @return PrimitiveInet **/ @@ -282,7 +338,7 @@ public static function inet($name) { return new PrimitiveInet($name); } - + /** * @return PrimitiveForm **/ @@ -290,7 +346,7 @@ public static function form($name) { return new PrimitiveForm($name); } - + /** * @return PrimitiveFormsList **/ @@ -298,7 +354,7 @@ public static function formsList($name) { return new PrimitiveFormsList($name); } - + /** * @return PrimitiveNoValue **/ @@ -306,7 +362,7 @@ public static function noValue($name) { return new PrimitiveNoValue($name); } - + /** * @return PrimitiveHttpUrl **/ @@ -314,39 +370,39 @@ public static function httpUrl($name) { return new PrimitiveHttpUrl($name); } - + /** * @return BasePrimitive **/ public static function prototyped($class, $propertyName, $name = null) { Assert::isInstance($class, 'Prototyped'); - + $proto = is_string($class) ? call_user_func(array($class, 'proto')) : $class->proto(); - + if (!$name) $name = $propertyName; - + return $proto->getPropertyByName($propertyName)-> makePrimitive($name); } - + /** * @return PrimitiveIdentifier **/ public static function prototypedIdentifier($class, $name = null) { Assert::isInstance($class, 'DAOConnected'); - + $dao = is_string($class) ? call_user_func(array($class, 'dao')) : $class->dao(); - + return self::prototyped($class, $dao->getIdName(), $name); } - + /** * @return PrimitiveIpAddress **/ @@ -354,7 +410,7 @@ public static function ipAddress($name) { return new PrimitiveIpAddress($name); } - + /** * @return PrimitiveIpRange */ @@ -362,5 +418,116 @@ public static function ipRange($name) { return new PrimitiveIpRange($name); } + + /** + * @return PrimitiveCidr + */ + public static function ipCidrRange($name) + { + return new PrimitiveCidr($name); + } + + /** + * @static + * @param $name + * @return PrimitiveUuidString + **/ + public static function uuid($name) + { + return new PrimitiveUuidString($name); + } + + /** + * @static + * @param $name + * @return PrimitiveUuidIdentifier + **/ + public static function uuidIdentifier($name) + { + return new PrimitiveUuidIdentifier($name); + } + + /** + * @static + * @param $name + * @return PrimitiveUuidIdentifierList + **/ + public static function uuidIdentifierList($name) + { + return new PrimitiveUuidIdentifierList($name); + } + + /** + * @static + * @return PrimitiveReCaptcha + **/ + public static function reCaptcha() + { + return new PrimitiveReCaptcha(); + } + + /** + * @return PrimitiveRule + **/ + public static function rule($name) + { + return new PrimitiveRule($name); + } + + /** + * @return PrimitiveEnum + **/ + public static function enum($name) + { + return new PrimitiveEnum($name); + } + + /** + * @return PrimitiveEnumByValue + **/ + public static function enumByValue($name) + { + return new PrimitiveEnumByValue($name); + } + + /** + * @return PrimitiveEnumList + **/ + public static function enumList($name) + { + return new PrimitiveEnumList($name); + } + + /** + * @return PrimitiveRegistry + **/ + public static function registry($name) + { + return new PrimitiveRegistry($name); + } + + /** + * @return PrimitiveRegistryByValue + **/ + public static function registryByValue($name) + { + return new PrimitiveRegistryByValue($name); + } + + /** + * @return PrimitiveRegistryList + **/ + public static function registryList($name) + { + return new PrimitiveRegistryList($name); + } + + public static function arrayOf(BasePrimitive $primitive) + { + $primitivePrimitives = new ArrayOfPrimitive($primitive->getName()); + $primitivePrimitives + ->setPrimitive($primitive); + return $primitivePrimitives; + } } ?> \ No newline at end of file diff --git a/core/Form/Primitives/ArrayOfPrimitive.class.php b/core/Form/Primitives/ArrayOfPrimitive.class.php new file mode 100644 index 0000000000..b47e108c83 --- /dev/null +++ b/core/Form/Primitives/ArrayOfPrimitive.class.php @@ -0,0 +1,78 @@ + + * @date 2015-11-27 + */ + +class ArrayOfPrimitive extends FiltrablePrimitive { + /** @var BasePrimitive */ + protected $primitive; + /** @var bool */ + protected $keepKeys = false; + + public function setPrimitive(BasePrimitive $primitive) + { + $this->primitive = $primitive; + $this->setRequired($primitive->isRequired()); + return $this; + } + + public function getPrimitive() + { + return $this->primitive; + } + + public function setKeepKeys($bool) + { + $this->keepKeys = $bool; + return $this; + } + + public function isKeepKeys() + { + return $this->keepKeys; + } + + public function import($scope) + { + if (!BasePrimitive::import($scope)) + return null; + + $this->imported = true; + $this->customError = null; + $this->value = []; + + foreach ($this->raw as $key => $element) { + $this->primitive->clean(); + if (!$this->primitive->importValue($element)) { + $this->imported = false; + } + $this->customError = $this->customError ?: $this->primitive->getCustomError(); + $value = $this->primitive->value; + if ($this->isKeepKeys()) { + $this->value[$key] = $value; + } else { + $this->value []= $value; + } + } + + if (!$this->imported) { + return false; + } + + $this->selfFilter(); + + if ( + is_array($this->value) + && !($this->min && count($this->value) < $this->min) + && !($this->max && count($this->value) > $this->max) + ) { + return true; + } else { + $this->value = null; + } + + return false; + } + +} \ No newline at end of file diff --git a/core/Form/Primitives/BaseObjectPrimitive.class.php b/core/Form/Primitives/BaseObjectPrimitive.class.php old mode 100644 new mode 100755 index 9df980e5ee..544961d6e7 --- a/core/Form/Primitives/BaseObjectPrimitive.class.php +++ b/core/Form/Primitives/BaseObjectPrimitive.class.php @@ -55,5 +55,15 @@ public function setDefault($default) return $this; } + + public function exportValue() + { + if ($this->value instanceof Stringable) { + return $this->value->toString(); + } + + throw new UnimplementedFeatureException('dont know how to export ' . $this->className); + } + } ?> \ No newline at end of file diff --git a/core/Form/Primitives/BasePrimitive.class.php b/core/Form/Primitives/BasePrimitive.class.php old mode 100644 new mode 100755 index d7cbe2f8b3..5f3c0b67a0 --- a/core/Form/Primitives/BasePrimitive.class.php +++ b/core/Form/Primitives/BasePrimitive.class.php @@ -190,7 +190,7 @@ public function getCustomError() return $this->customError; } - protected function import($scope) + public function import($scope) { if ( !empty($scope[$this->name]) @@ -208,5 +208,14 @@ protected function import($scope) return null; } + + public function arrayOf($minElements = null, $maxElements = null) + { + $arrayOfPrimitive = Primitive::arrayOf($this); + $arrayOfPrimitive + ->setMin($minElements) + ->setMax($maxElements); + return $arrayOfPrimitive; + } } ?> \ No newline at end of file diff --git a/core/Form/Primitives/ComplexPrimitive.class.php b/core/Form/Primitives/ComplexPrimitive.class.php old mode 100644 new mode 100755 index f5dfdaa3fb..aa739efb39 --- a/core/Form/Primitives/ComplexPrimitive.class.php +++ b/core/Form/Primitives/ComplexPrimitive.class.php @@ -74,8 +74,8 @@ public function setAnyState() } // implement me, child :-) - abstract protected function importSingle($scope); - abstract protected function importMarried($scope); + abstract public function importSingle($scope); + abstract public function importMarried($scope); public function import($scope) { diff --git a/core/Form/Primitives/DateRangeList.class.php b/core/Form/Primitives/DateRangeList.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/ExplodedPrimitive.class.php b/core/Form/Primitives/ExplodedPrimitive.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/FiltrablePrimitive.class.php b/core/Form/Primitives/FiltrablePrimitive.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/IdentifiablePrimitive.class.php b/core/Form/Primitives/IdentifiablePrimitive.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/ListedPrimitive.class.php b/core/Form/Primitives/ListedPrimitive.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveAlias.class.php b/core/Form/Primitives/PrimitiveAlias.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveAnyType.class.php b/core/Form/Primitives/PrimitiveAnyType.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveArray.class.php b/core/Form/Primitives/PrimitiveArray.class.php old mode 100644 new mode 100755 index 32b985d8b6..b3b58be862 --- a/core/Form/Primitives/PrimitiveArray.class.php +++ b/core/Form/Primitives/PrimitiveArray.class.php @@ -12,7 +12,7 @@ /** * @ingroup Primitives **/ - final class PrimitiveArray extends FiltrablePrimitive + class PrimitiveArray extends FiltrablePrimitive { /** * Fetching strategy for incoming containers: diff --git a/core/Form/Primitives/PrimitiveArrayOfFiles.class.php b/core/Form/Primitives/PrimitiveArrayOfFiles.class.php new file mode 100644 index 0000000000..be1e839deb --- /dev/null +++ b/core/Form/Primitives/PrimitiveArrayOfFiles.class.php @@ -0,0 +1,78 @@ +name]) && is_array($scope[$this->name])) { + foreach($scope[$this->name] as $field => $info) { + foreach ($info as $index => $value) { + $documents[$index][$field] = $value; + } + } + $scope[$this->name] = $documents; + } else { + return null; + } + + foreach ($scope[$this->name] as $tempFile) { + + if ($tempFile['error'] && $tempFile['error'] == UPLOAD_ERR_NO_FILE) { + return null; + } + + if (isset($tempFile['tmp_name'])) { + $file = $tempFile['tmp_name']; + } else { + return false; + } + + if (is_readable($file) && $this->checkUploaded($file)) { + $size = filesize($file); + } else { + return false; + } + + if (class_exists('finfo')) { + $finfo = new finfo(FILEINFO_MIME_TYPE); + $this->mimeType = $finfo->file($file); + } else { + $this->mimeType = $tempFile['type']; + } + + if (!$this->isAllowedMimeType()) { + return false; + } + + if ( ($this->max && ($size > $this->max)) + && ($this->min && ($size < $this->min)) + ) { + return false; + } + + } + + $this->raw = $scope; + $this->value = $documents; + $this->imported = true; + return true; + } + +} +?> \ No newline at end of file diff --git a/core/Form/Primitives/PrimitiveBinary.class.php b/core/Form/Primitives/PrimitiveBinary.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveBoolean.class.php b/core/Form/Primitives/PrimitiveBoolean.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveCidr.class.php b/core/Form/Primitives/PrimitiveCidr.class.php new file mode 100644 index 0000000000..c4a7cc6728 --- /dev/null +++ b/core/Form/Primitives/PrimitiveCidr.class.php @@ -0,0 +1,13 @@ + + * @date 2013.12.02 + */ + +/** + * @ingroup Primitives + **/ +final class PrimitiveCidr extends BaseObjectPrimitive +{ + protected $className = 'IpCidrRange'; +} \ No newline at end of file diff --git a/core/Form/Primitives/PrimitiveClass.class.php b/core/Form/Primitives/PrimitiveClass.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveDate.class.php b/core/Form/Primitives/PrimitiveDate.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveDateRange.class.php b/core/Form/Primitives/PrimitiveDateRange.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveEnum.class.php b/core/Form/Primitives/PrimitiveEnum.class.php new file mode 100644 index 0000000000..3fdecb9118 --- /dev/null +++ b/core/Form/Primitives/PrimitiveEnum.class.php @@ -0,0 +1,132 @@ +value) + return ClassUtils::callStaticMethod(get_class($this->value).'::getList'); + elseif ($this->default) + return ClassUtils::callStaticMethod(get_class($this->default).'::getList'); + else { + $object = new $this->className( + ClassUtils::callStaticMethod($this->className.'::getAnyId') + ); + + return $object->getObjectList(); + } + + Assert::isUnreachable(); + } + + /** + * @throws WrongArgumentException + * @return PrimitiveEnum + **/ + public function of($class) + { + $className = $this->guessClassName($class); + + Assert::classExists($className); + + Assert::isInstance($className, 'Enum'); + + $this->className = $className; + + return $this; + } + + public function importValue(/* Identifiable */ $value) + { + if ($value) + Assert::isEqual(get_class($value), $this->className); + else + return parent::importValue(null); + + return $this->import(array($this->getName() => $value->getId())); + } + + public function import($scope) + { + $result = parent::import($scope); + + if ($result === true) { + try { + $this->value = $this->makeEnumById($this->value); + } catch (MissingElementException $e) { + $this->value = null; + + return false; + } + + return true; + } + + return $result; + } + + /** + * @param $list + * @throws UnsupportedMethodException + */ + public function setList($list) + { + throw new UnsupportedMethodException('you cannot set list here, it is impossible, because list getted from enum classes'); + } + + /** + * @return null|string + */ + public function getChoiceValue() + { + if( + ($value = $this->getValue() ) && + $value instanceof Enum + ) + return $value->getName(); + + return null; + } + + + /** + * @return Enum|mixed|null + */ + public function getActualChoiceValue() + { + if( + !$this->getChoiceValue() && + $this->getDefault() + ) + return $this->getDefault()->getName(); + + return null; + } + + /** + * @param $id + * @return Enum|mixed + */ + protected function makeEnumById($id) + { + if (!$this->className) + throw new WrongStateException( + "no class defined for PrimitiveEnum '{$this->name}'" + ); + + return new $this->className($id); + } + } +?> \ No newline at end of file diff --git a/core/Form/Primitives/PrimitiveEnumByValue.class.php b/core/Form/Primitives/PrimitiveEnumByValue.class.php new file mode 100644 index 0000000000..ac85b6537d --- /dev/null +++ b/core/Form/Primitives/PrimitiveEnumByValue.class.php @@ -0,0 +1,48 @@ +className) + throw new WrongStateException( + "no class defined for PrimitiveEnum '{$this->name}'" + ); + + if (isset($scope[$this->name])) { + $scopedValue = urldecode($scope[$this->name]); + + $names = ClassUtils::callStaticMethod($this->className.'::getNameList'); + + foreach ($names as $key => $value) { + if ($value == $scopedValue) { + try { + $this->value = new $this->className($key); + } catch (MissingElementException $e) { + $this->value = null; + return false; + } + + return true; + } + } + + return false; + } + + return null; + } + } +?> \ No newline at end of file diff --git a/core/Form/Primitives/PrimitiveEnumList.class.php b/core/Form/Primitives/PrimitiveEnumList.class.php new file mode 100644 index 0000000000..26180b5727 --- /dev/null +++ b/core/Form/Primitives/PrimitiveEnumList.class.php @@ -0,0 +1,113 @@ +value = array(); + + return $this; + } + + /** + * @return PrimitiveEnumList + **/ + public function setValue(/* Enum */ $value) + { + if ($value) { + Assert::isArray($value); + Assert::isInstance(current($value), 'Enum'); + } + + $this->value = $value; + + return $this; + } + + public function importValue($value) + { + if (is_array($value)) { + try { + Assert::isInteger(current($value)); + + return $this->import( + array($this->name => $value) + ); + } catch (WrongArgumentException $e) { + return $this->import( + array($this->name => ArrayUtils::getIdsArray($value)) + ); + } + } + + return parent::importValue($value); + } + + public function import($scope) + { + if (!$this->className) + throw new WrongStateException( + "no class defined for PrimitiveIdentifierList '{$this->name}'" + ); + + if (!BasePrimitive::import($scope)) + return null; + + if (!is_array($scope[$this->name])) + return false; + + $list = array_unique($scope[$this->name]); + + $values = array(); + + foreach ($list as $id) { + if (!Assert::checkInteger($id)) + return false; + + $values[] = $id; + } + + $objectList = array(); + + foreach ($values as $value) { + $className = $this->className; + $objectList[] = new $className($value); + } + + if (count($objectList) == count($values)) { + $this->value = $objectList; + return true; + } + + return false; + } + + public function exportValue() + { + if (!$this->value) + return null; + + return ArrayUtils::getIdsArray($this->value); + } + } +?> \ No newline at end of file diff --git a/core/Form/Primitives/PrimitiveEnumeration.class.php b/core/Form/Primitives/PrimitiveEnumeration.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveEnumerationByValue.class.php b/core/Form/Primitives/PrimitiveEnumerationByValue.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveEnumerationList.class.php b/core/Form/Primitives/PrimitiveEnumerationList.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveFile.class.php b/core/Form/Primitives/PrimitiveFile.class.php old mode 100644 new mode 100755 index 87697db2db..e7d76eddb0 --- a/core/Form/Primitives/PrimitiveFile.class.php +++ b/core/Form/Primitives/PrimitiveFile.class.php @@ -16,11 +16,11 @@ **/ class PrimitiveFile extends RangedPrimitive { - private $originalName = null; - private $mimeType = null; + protected $originalName = null; + protected $mimeType = null; - private $allowedMimeTypes = array(); - private $checkUploaded = true; + protected $allowedMimeTypes = array(); + protected $checkUploaded = true; public function getOriginalName() { @@ -117,14 +117,19 @@ public function import($scope) $file = $scope[$this->name]['tmp_name']; else return false; - + if (is_readable($file) && $this->checkUploaded($file)) $size = filesize($file); else return false; - - $this->mimeType = $scope[$this->name]['type']; - + + if (class_exists('finfo')) { + $finfo = new finfo(FILEINFO_MIME_TYPE); + $this->mimeType = $finfo->file($file); + } else { + $this->mimeType = $scope[$this->name]['type']; + } + if (!$this->isAllowedMimeType()) return false; @@ -171,8 +176,8 @@ public function isCheckUploaded() { return $this->checkUploaded; } - - private function checkUploaded($file) + + protected function checkUploaded($file) { return !$this->checkUploaded || is_uploaded_file($file); } diff --git a/core/Form/Primitives/PrimitiveFloat.class.php b/core/Form/Primitives/PrimitiveFloat.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveForm.class.php b/core/Form/Primitives/PrimitiveForm.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveFormsList.class.php b/core/Form/Primitives/PrimitiveFormsList.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveHstore.class.php b/core/Form/Primitives/PrimitiveHstore.class.php old mode 100644 new mode 100755 index 1b05b48583..3834229f36 --- a/core/Form/Primitives/PrimitiveHstore.class.php +++ b/core/Form/Primitives/PrimitiveHstore.class.php @@ -12,7 +12,7 @@ /** * @ingroup Primitives **/ - final class PrimitiveHstore extends BasePrimitive + class PrimitiveHstore extends BasePrimitive { protected $formMapping = array(); diff --git a/core/Form/Primitives/PrimitiveHttpUrl.class.php b/core/Form/Primitives/PrimitiveHttpUrl.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveIdentifier.class.php b/core/Form/Primitives/PrimitiveIdentifier.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveIdentifierList.class.php b/core/Form/Primitives/PrimitiveIdentifierList.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveImage.class.php b/core/Form/Primitives/PrimitiveImage.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveInet.class.php b/core/Form/Primitives/PrimitiveInet.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveInteger.class.php b/core/Form/Primitives/PrimitiveInteger.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveIntegerIdentifier.class.php b/core/Form/Primitives/PrimitiveIntegerIdentifier.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveIpAddress.class.php b/core/Form/Primitives/PrimitiveIpAddress.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveIpRange.class.php b/core/Form/Primitives/PrimitiveIpRange.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveJson.class.php b/core/Form/Primitives/PrimitiveJson.class.php new file mode 100644 index 0000000000..b80882e751 --- /dev/null +++ b/core/Form/Primitives/PrimitiveJson.class.php @@ -0,0 +1,96 @@ +fetchMode = $ternary; + + return $this; + } + + public function import($scope) { + if (!BasePrimitive::import($scope)) + return null; + + if (!is_array($scope[$this->name])) { + try { + $this->value = json_decode($scope[$this->name], 1); //to assoc array + } catch (Exception $e) { + //Only UTF-8, for instance! + throw new WrongArgumentException('String in json field should be valid JSON'); + } + } else { + $this->value = $scope[$this->name]; + } + $this->selfFilter(); + + if ( + is_array($this->value) + && !($this->min && count($this->value) < $this->min) + && !($this->max && count($this->value) > $this->max) + ) { + return true; + } else { + $this->value = null; + } + + return false; + } + + public function importValue($value) { + if ($value instanceof UnifiedContainer) { + if ( + ($this->fetchMode !== null) + && ($value->getParentObject()->getId()) + ) { + if ($value->isLazy() === $this->fetchMode) { + $value = $value->getList(); + } else { + $className = get_class($value); + + $containter = new $className( + $value->getParentObject(), + $this->fetchMode + ); + + $value = $containter->getList(); + } + } elseif (!$value->isFetched()) + return null; + } + + return $this->import(array($this->getName() => $value)); + } + + public function exportValue() + { + return json_encode(parent::exportValue()); + } + + +} \ No newline at end of file diff --git a/core/Form/Primitives/PrimitiveList.class.php b/core/Form/Primitives/PrimitiveList.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveMultiList.class.php b/core/Form/Primitives/PrimitiveMultiList.class.php old mode 100644 new mode 100755 index a8844a25c3..2a0ca278ab --- a/core/Form/Primitives/PrimitiveMultiList.class.php +++ b/core/Form/Primitives/PrimitiveMultiList.class.php @@ -46,8 +46,9 @@ public function setDefault($default) foreach ($default as $index) Assert::isTrue(array_key_exists($index, $this->list)); - - return parent::setDefault($default); + + $this->default = $default; + return $this; } public function import($scope) diff --git a/core/Form/Primitives/PrimitiveNoValue.class.php b/core/Form/Primitives/PrimitiveNoValue.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveNumber.class.php b/core/Form/Primitives/PrimitiveNumber.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitivePlainList.class.php b/core/Form/Primitives/PrimitivePlainList.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitivePolymorphicIdentifier.class.php b/core/Form/Primitives/PrimitivePolymorphicIdentifier.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveRange.class.php b/core/Form/Primitives/PrimitiveRange.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveReCaptcha.class.php b/core/Form/Primitives/PrimitiveReCaptcha.class.php new file mode 100755 index 0000000000..3b2405b4a8 --- /dev/null +++ b/core/Form/Primitives/PrimitiveReCaptcha.class.php @@ -0,0 +1,174 @@ + + * @date 2012.02.20 + */ +class PrimitiveReCaptcha extends BasePrimitive { + + CONST + RECAPTCHA_API_SERVER = '//www.google.com/recaptcha/api', + RECAPTCHA_VERIFY_SERVER = 'www.google.com', + + RECAPTCHA_RESPONSE_FIELD = 'recaptcha_response_field', + RECAPTCHA_CHALLENGE_FIELD = 'recaptcha_challenge_field' + ; + + protected $reCaptchaPrivateKey = null; + + public function __construct() { + $this->name = self::RECAPTCHA_RESPONSE_FIELD; + } + + /** + * @param string $key + * @return PrimitiveReCaptcha + */ + public function setReCaptchaPrivateKey( $key ) { + $this->reCaptchaPrivateKey = $key; + return $this; + } + + public function import($scope) { + if (!BasePrimitive::import($scope)) + return null; + + if (!is_scalar($scope[$this->name])) + return false; + + $this->value = (string) $scope[$this->name]; + + if ( !isset($_POST[self::RECAPTCHA_CHALLENGE_FIELD]) ) { + return false; + } + $challenge = $_POST[self::RECAPTCHA_CHALLENGE_FIELD]; + + if ( + is_string($this->value) + // zero is quite special value here + && !empty($this->value) + && is_string($challenge) + && self::recaptcha_check_answer( + $this->reCaptchaPrivateKey, + $_SERVER["REMOTE_ADDR"], + $challenge, + $this->value + ) + ) { + return true; + } else { + $this->value = null; + } + + return false; + } + + /** + * Submits an HTTP POST to a reCAPTCHA server + * @param string $host + * @param string $path + * @param array $data + * @param int port + * @return array response + */ + protected static function _recaptcha_http_post($host, $path, $data, $port = 80) { + $req = ""; + foreach ( $data as $key => $value ) { + $req .= $key . '=' . urlencode( stripslashes($value) ) . '&'; + } + // Cut the last '&' + $req=substr($req,0,strlen($req)-1); + + $options = array( + CURLOPT_URL => 'http://'.$host.':'.$port.$path, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_USERAGENT => 'reCAPTCHA/PHP', + CURLOPT_HTTPHEADER => array('Content-Type: application/x-www-form-urlencoded', 'Content-Length: '.strlen($req)), + CURLOPT_POSTFIELDS => $req, + CURLOPT_TIMEOUT => 3, + ); + + // переменная под результат + $result = false; + + // делаем запрос + $handler = curl_init(); + curl_setopt_array($handler, $options); + $response = curl_exec($handler); + if( curl_errno($handler)==0 && curl_getinfo($handler, CURLINFO_HTTP_CODE)==200 ) { + $response = explode("\n", $response); + if( isset($response[0]) && trim($response[0])=='true' ) { + $result = true; + } + } + curl_close($handler); + + return $result; + } + + /** + * Gets the challenge HTML (javascript and non-javascript version). + * This is called from the browser, and the resulting reCAPTCHA HTML widget + * is embedded within the HTML form it was called from. + * @param string $pubkey A public key for reCAPTCHA + * @param string $error The error given by reCAPTCHA (optional, default is null) + + * @return string - The HTML to be embedded in the user's form. + */ + public static function getHtmlCode ($pubKey, $lang = null, $error = null) { + if ($pubKey == null || $pubKey == '') { + die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); + } + + $langPart = ""; + if ($lang) { + $langPart = "&hl=" . $lang; + } + + $errorPart = ""; + if ($error) { + $errorPart = "&error=" . $error; + } + + return ''; + } + + /** + * Calls an HTTP POST function to verify if the user's guess was correct + * @param string $privkey + * @param string $remoteip + * @param string $challenge + * @param string $response + * @param array $extra_params an array of extra variables to post to the server + * @return boolean + */ + protected static function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array()) { + + if ($privkey == null || $privkey == '') { + die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); + } + + if ($remoteip == null || $remoteip == '') { + die ("For security reasons, you must pass the remote ip to reCAPTCHA"); + } + + //discard spam submissions + if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) { + return false; + } + + return + self::_recaptcha_http_post ( + self::RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify", + array ( + 'privatekey' => $privkey, + 'remoteip' => $remoteip, + 'challenge' => $challenge, + 'response' => $response + ) + $extra_params + ); + } + +} diff --git a/core/Form/Primitives/PrimitiveRegistry.class.php b/core/Form/Primitives/PrimitiveRegistry.class.php new file mode 100644 index 0000000000..15be596caa --- /dev/null +++ b/core/Form/Primitives/PrimitiveRegistry.class.php @@ -0,0 +1,139 @@ +value) + return ClassUtils::callStaticMethod(get_class($this->value).'::getList'); + elseif ($this->default) + return ClassUtils::callStaticMethod(get_class($this->default).'::getList'); + else { + $object = new $this->className( + ClassUtils::callStaticMethod($this->className.'::getAnyId') + ); + + return $object->getObjectList(); + } + + Assert::isUnreachable(); + } + + /** + * @throws WrongArgumentException + * @return PrimitiveRegistry + **/ + public function of($class) + { + $className = $this->guessClassName($class); + + Assert::classExists($className); + + Assert::isInstance($className, 'Registry'); + + $this->className = $className; + + return $this; + } + + public function importValue(/* Identifiable */ $value) + { + if ($value) + Assert::isEqual(get_class($value), $this->className); + else + return parent::importValue(null); + + return $this->import(array($this->getName() => $value->getId())); + } + + public function import($scope) + { + $result = parent::import($scope); + + if ($result === true) { + try { + $this->value = $this->makeRegistryById($this->value); + } catch (MissingElementException $e) { + $this->value = null; + + return false; + } + + return true; + } + + return $result; + } + + /** + * @param $list + * @throws UnsupportedMethodException + */ + public function setList($list) + { + throw new UnsupportedMethodException('you cannot set list here, it is impossible, because list getted from registry classes'); + } + + /** + * @return null|string + */ + public function getChoiceValue() + { + if( + ($value = $this->getValue() ) && + $value instanceof Registry + ) + return $value->getName(); + + return null; + } + + + /** + * @return Registry|mixed|null + */ + public function getActualChoiceValue() + { + if( + !$this->getChoiceValue() && + $this->getDefault() + ) + return $this->getDefault()->getName(); + + return null; + } + + /** + * @param $id + * @return Registry|mixed + */ + protected function makeRegistryById($id) + { + if (!$this->className) + throw new WrongStateException( + "no class defined for PrimitiveRegistry '{$this->name}'" + ); + + return new $this->className($id); + } + } \ No newline at end of file diff --git a/core/Form/Primitives/PrimitiveRegistryByValue.class.php b/core/Form/Primitives/PrimitiveRegistryByValue.class.php new file mode 100644 index 0000000000..c67fb08316 --- /dev/null +++ b/core/Form/Primitives/PrimitiveRegistryByValue.class.php @@ -0,0 +1,47 @@ +className) + throw new WrongStateException( + "no class defined for PrimitiveRegistry '{$this->name}'" + ); + + if (isset($scope[$this->name])) { + $scopedValue = urldecode($scope[$this->name]); + + $names = ClassUtils::callStaticMethod($this->className.'::getNameList'); + + foreach ($names as $key => $value) { + if ($value == $scopedValue) { + try { + $this->value = new $this->className($key); + } catch (MissingElementException $e) { + $this->value = null; + return false; + } + + return true; + } + } + + return false; + } + + return null; + } + } diff --git a/core/Form/Primitives/PrimitiveRegistryList.class.php b/core/Form/Primitives/PrimitiveRegistryList.class.php new file mode 100644 index 0000000000..e8a32fc450 --- /dev/null +++ b/core/Form/Primitives/PrimitiveRegistryList.class.php @@ -0,0 +1,112 @@ +value = array(); + + return $this; + } + + /** + * @return PrimitiveRegistryList + **/ + public function setValue(/* Registry */ $value) + { + if ($value) { + Assert::isArray($value); + Assert::isInstance(current($value), 'Registry'); + } + + $this->value = $value; + + return $this; + } + + public function importValue($value) + { + if (is_array($value)) { + try { + Assert::isScalar(current($value)); + + return $this->import( + array($this->name => $value) + ); + } catch (WrongArgumentException $e) { + return $this->import( + array($this->name => ArrayUtils::getIdsArray($value)) + ); + } + } + + return parent::importValue($value); + } + + public function import($scope) + { + if (!$this->className) + throw new WrongStateException( + "no class defined for PrimitiveIdentifierList '{$this->name}'" + ); + + if (!BasePrimitive::import($scope)) + return null; + + if (!is_array($scope[$this->name])) + return false; + + $list = array_unique($scope[$this->name]); + + $values = array(); + + foreach ($list as $id) { + if (!Assert::checkScalar($id)) + return false; + + $values[] = $id; + } + + $objectList = array(); + + foreach ($values as $value) { + $className = $this->className; + $objectList[] = new $className($value); + } + + if (count($objectList) == count($values)) { + $this->value = $objectList; + return true; + } + + return false; + } + + public function exportValue() + { + if (!$this->value) + return null; + + return ArrayUtils::getIdsArray($this->value); + } + } \ No newline at end of file diff --git a/core/Form/Primitives/PrimitiveRule.class.php b/core/Form/Primitives/PrimitiveRule.class.php new file mode 100755 index 0000000000..9d5bbdfbcc --- /dev/null +++ b/core/Form/Primitives/PrimitiveRule.class.php @@ -0,0 +1,51 @@ +form = $form; + + return $this; + } + + /** + * @return PrimitiveRule + **/ + public function setExpression(LogicalObject $exp) + { + $this->expression = $exp; + + return $this; + } + + public function import($scope) + { + Assert::isNotNull($this->form); + Assert::isNotNull($this->expression); + + return $this->expression->toBoolean($this->form); + } + } +?> \ No newline at end of file diff --git a/core/Form/Primitives/PrimitiveScalarIdentifier.class.php b/core/Form/Primitives/PrimitiveScalarIdentifier.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveString.class.php b/core/Form/Primitives/PrimitiveString.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveTernary.class.php b/core/Form/Primitives/PrimitiveTernary.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveTime.class.php b/core/Form/Primitives/PrimitiveTime.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveTimestamp.class.php b/core/Form/Primitives/PrimitiveTimestamp.class.php old mode 100644 new mode 100755 index d3fb56dab7..23413801db --- a/core/Form/Primitives/PrimitiveTimestamp.class.php +++ b/core/Form/Primitives/PrimitiveTimestamp.class.php @@ -12,7 +12,7 @@ /** * @ingroup Primitives **/ - final class PrimitiveTimestamp extends PrimitiveDate + class PrimitiveTimestamp extends PrimitiveDate { const HOURS = 'hrs'; const MINUTES = 'min'; @@ -73,5 +73,27 @@ protected function getObjectName() { return 'Timestamp'; } + + public function exportValue() + { + $parent = parent::exportValue(); + + if(is_array($parent)) { + + if($this->value) { + $parent[static::HOURS] = $this->value->getHour(); + $parent[static::MINUTES] = $this->value->getMinute(); + $parent[static::SECONDS] = $this->value->getSecond(); + + } else { + + $parent[static::HOURS] = null; + $parent[static::MINUTES] = null; + $parent[static::SECONDS] = null; + } + } + + return $parent; + } } ?> \ No newline at end of file diff --git a/core/Form/Primitives/PrimitiveTimestampRange.class.php b/core/Form/Primitives/PrimitiveTimestampRange.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/PrimitiveTimestampTZ.class.php b/core/Form/Primitives/PrimitiveTimestampTZ.class.php new file mode 100644 index 0000000000..217f57ce5f --- /dev/null +++ b/core/Form/Primitives/PrimitiveTimestampTZ.class.php @@ -0,0 +1,91 @@ +name][self::DAY], + $scope[$this->name][self::MONTH], + $scope[$this->name][self::YEAR], + $scope[$this->name][self::HOURS], + $scope[$this->name][self::MINUTES], + $scope[$this->name][self::SECONDS], + $scope[$this->name][self::ZONE] + ) + && is_array($scope[$this->name]) + ) { + if ($this->isEmpty($scope)) + return !$this->isRequired(); + + $zone = $scope[$this->name][self::ZONE]; + + $hours = (int) $scope[$this->name][self::HOURS]; + $minutes = (int) $scope[$this->name][self::MINUTES]; + $seconds = (int) $scope[$this->name][self::SECONDS]; + + $year = (int) $scope[$this->name][self::YEAR]; + $month = (int) $scope[$this->name][self::MONTH]; + $day = (int) $scope[$this->name][self::DAY]; + + if (!checkdate($month, $day, $year)) + return false; + + try { + $stamp = new TimestampTZ( + $year.'-'.$month.'-'.$day.' ' + .$hours.':'.$minutes.':'.$seconds + .' '.$zone + ); + } catch (WrongArgumentException $e) { + return false; + } + + if ($this->checkRanges($stamp)) { + $this->value = $stamp; + return true; + } + } + + return false; + } + + protected function getObjectName() + { + return 'TimestampTZ'; + } + + public function exportValue() + { + $parent = parent::exportValue(); + + if(is_array($parent)) { + if($this->value) { + $parent[static::ZONE] = $this->value->getDateTime()->getTimezone()->getName(); + + } else { + $parent[static::ZONE] = null; + } + + } + + return $parent; + } +} +?> diff --git a/core/Form/Primitives/PrimitiveUuidIdentifier.class.php b/core/Form/Primitives/PrimitiveUuidIdentifier.class.php new file mode 100755 index 0000000000..e6c0d21f26 --- /dev/null +++ b/core/Form/Primitives/PrimitiveUuidIdentifier.class.php @@ -0,0 +1,27 @@ +value = array(); + + return $this; + } + + /** + * @return PrimitiveUuidIdentifierList + **/ + public function setValue($value) + { + if ($value) { + Assert::isArray($value); + Assert::isInstance(current($value), $this->className); + } + + $this->value = $value; + + return $this; + } + + public function importValue($value) + { + if ($value instanceof UnifiedContainer) { + if ($value->isLazy()) + return $this->import( + array($this->name => $value->getList()) + ); + elseif ( + $value->getParentObject()->getId() + && ($list = $value->getList()) + ) { + return $this->import( + array($this->name => ArrayUtils::getIdsArray($list)) + ); + } else { + return parent::importValue(null); + } + } + + if (is_array($value)) { + try { + Assert::isUniversalUniqueIdentifier(current($value)); + + return $this->import( + array($this->name => $value) + ); + } catch (WrongArgumentException $e) { + return $this->import( + array($this->name => ArrayUtils::getIdsArray($value)) + ); + } + } + + return parent::importValue($value); + } + + public function import($scope) + { + if (!$this->className) + throw new WrongStateException( + "no class defined for PrimitiveUuidIdentifierList '{$this->name}'" + ); + + if (!BasePrimitive::import($scope)) + return null; + + if (!is_array($scope[$this->name])) + return false; + + $list = array_unique($scope[$this->name]); + + $values = array(); + + foreach ($list as $id) { + if (!Assert::checkUniversalUniqueIdentifier($id)) + return false; + + $values[] = $id; + } + + $objectList = $this->dao()->getListByIds($values); + + if ( + count($objectList) == count($values) + && !($this->min && count($values) < $this->min) + && !($this->max && count($values) > $this->max) + ) { + $this->value = $objectList; + return true; + } + + return false; + } + + public function exportValue() + { + if (!$this->value) + return null; + + return ArrayUtils::getIdsArray($this->value); + } + +} diff --git a/core/Form/Primitives/PrimitiveUuidString.class.php b/core/Form/Primitives/PrimitiveUuidString.class.php new file mode 100755 index 0000000000..ed7d2dadd0 --- /dev/null +++ b/core/Form/Primitives/PrimitiveUuidString.class.php @@ -0,0 +1,32 @@ +setAllowedPattern(self::UUID_PATTERN); + } + +} diff --git a/core/Form/Primitives/RangedPrimitive.class.php b/core/Form/Primitives/RangedPrimitive.class.php old mode 100644 new mode 100755 diff --git a/core/Form/Primitives/TimeList.class.php b/core/Form/Primitives/TimeList.class.php old mode 100644 new mode 100755 diff --git a/core/Form/RegulatedForm.class.php b/core/Form/RegulatedForm.class.php old mode 100644 new mode 100755 diff --git a/core/Logic/ArrayExpression.class.php b/core/Logic/ArrayExpression.class.php new file mode 100644 index 0000000000..8ba07d942f --- /dev/null +++ b/core/Logic/ArrayExpression.class.php @@ -0,0 +1,181 @@ + + * @author Anton Gurov + * @date 2014.11.27 + * refer to + * http://www.postgresql.org/docs/current/interactive/functions-array.html#ARRAY-OPERATORS-TABLE + * for list of array operators + */ +class ArrayExpression implements LogicalObject, MappableObject { + + const FIELD = null; + const ANY = 'ANY'; + const ALL = 'ALL'; + + const CONTAINS = '@>'; + const IS_CONTAINED_BY = '<@'; + + const OVERLAP = '&&'; + + const CONCAT = '||'; + + protected + $subject = null, + $field = null, + $logic = null; + + protected function __construct($subject, $field, $logic) { + $this->subject = $subject; + $this->field = $field; + $this->logic = $logic; + } + + public static function field($subject, $field) + { + return new self($subject, $field, self::FIELD); + } + + public static function any($subject) + { + return new self($subject, null, self::ANY); + } + + public static function anyEq($field, $value) { + return Expression::eq(DBValue::create($value), self::any($field)); + } + + public static function all($subject) + { + return new self($subject, null, self::ALL); + } + + public static function allEq($subject, $value) { + return Expression::eq(DBValue::create($value), self::all($subject)); + } + + /** + * @param $field + * @param $value + * @return ArrayExpression + */ + public static function contains($field, $value) { + return new self( + $field, $value, self::CONTAINS + ); + } + + /** + * @param $field + * @param $value + * @return ArrayExpression + */ + public static function isContainedBy($field, $value) { + return new self( + $field, $value, self::IS_CONTAINED_BY + ); + } + + /** + * @param $field + * @param $value + * @return ArrayExpression + */ + public static function overlap($field, $value) { + return new self( + $field, $value, self::OVERLAP + ); + } + + /** + * @param $field + * @param $value + * @return ArrayExpression + */ + public static function concat($field, $value) { + return new self( + $field, $value, self::CONCAT + ); + } + + + public function toDialectString(Dialect $dialect) + { + switch ($this->logic) { + //Обращение напрямую к полю + case self::FIELD: + return $tableField; + + //Операторы, которые между значениями + case self::CONTAINS: + case self::IS_CONTAINED_BY: + case self::OVERLAP: + case self::CONCAT: + if (!is_array($this->field->getValue())) { + throw new WrongArgumentException("Trying to use array functions with non-array value:" + . print_r($this->field,1)); + } + $right = "ARRAY [".implode(',', $this->field->getValue())."]"; // @todo Dangerous! + return + '(' + .$dialect->toFieldString($this->subject) + .' '.$dialect->logicToString($this->logic).' ' + .$right //Dangerous! + .')'; + + // ANY и ALL + default: + $tableField = $dialect->toFieldString($this->subject); + if( !is_null($this->field) ) { + $tableField .= "[{$this->field}]"; + } + return + $dialect->logicToString($this->logic). + '(' + .$tableField + .')'; + + } + + } + + /** + * @return BinaryExpression + **/ + public function toMapped(ProtoDAO $dao, JoinCapableQuery $query) + { + return new self( + $dao->guessAtom($this->subject, $query), + $this->field, + $this->logic + ); + } + + public function toBoolean(Form $form) { + $left = $form->toFormValue($this->field); + $right = $form->toFormValue($this->subject); + + $both = + (null !== $left) + && (null !== $right); + + switch ($this->logic) { + case self::CONTAINS: + return $both && in_array($left, $right); + + case self::IS_CONTAINED_BY: + return $both && in_array($right, $left); + + case self::OVERLAP: + $intersection = array_intersect($left , $right); + return !empty($intersection); + + case self::CONCAT: + throw new UnsupportedMethodException("{$this->logic} cannot be converted to boolean "); + + default: + throw new UnsupportedMethodException('Method toBoolean is not supported by ArrayExpression'); + } + } +} \ No newline at end of file diff --git a/core/Logic/BinaryExpression.class.php b/core/Logic/BinaryExpression.class.php old mode 100644 new mode 100755 index 921ea21a9e..07bc11cb7f --- a/core/Logic/BinaryExpression.class.php +++ b/core/Logic/BinaryExpression.class.php @@ -12,11 +12,11 @@ /** * @ingroup Logic **/ - final class BinaryExpression implements LogicalObject, MappableObject + class BinaryExpression implements LogicalObject, MappableObject { const EQUALS = '='; const NOT_EQUALS = '!='; - + const EXPRESSION_AND = 'AND'; const EXPRESSION_OR = 'OR'; @@ -33,39 +33,39 @@ final class BinaryExpression implements LogicalObject, MappableObject const SIMILAR_TO = 'SIMILAR TO'; const NOT_SIMILAR_TO = 'NOT SIMILAR TO'; - + const ADD = '+'; const SUBSTRACT = '-'; const MULTIPLY = '*'; const DIVIDE = '/'; const MOD = '%'; - + private $left = null; private $right = null; private $logic = null; - + public function __construct($left, $right, $logic) { $this->left = $left; $this->right = $right; $this->logic = $logic; } - + public function getLeft() { return $this->left; } - + public function getRight() { return $this->right; } - + public function getLogic() { return $this->logic; } - + public function toDialectString(Dialect $dialect) { return @@ -75,7 +75,7 @@ public function toDialectString(Dialect $dialect) .$dialect->toValueString($this->right) .')'; } - + /** * @return BinaryExpression **/ @@ -87,16 +87,16 @@ public function toMapped(ProtoDAO $dao, JoinCapableQuery $query) $this->logic ); } - + public function toBoolean(Form $form) { $left = $form->toFormValue($this->left); $right = $form->toFormValue($this->right); - + $both = (null !== $left) && (null !== $right); - + switch ($this->logic) { case self::EQUALS: return $both && ($left == $right); @@ -118,25 +118,25 @@ public function toBoolean(Form $form) case self::EXPRESSION_AND: return $both && ($left && $right); - + case self::EXPRESSION_OR: return $both && ($left || $right); - + case self::ADD: return $both && ($left + $right); - + case self::SUBSTRACT: return $both && ($left - $right); - + case self::MULTIPLY: return $both && ($left * $right); - + case self::DIVIDE: return $both && $right && ($left / $right); - + case self::MOD: return $both && $right && ($left % $right); - + default: throw new UnsupportedMethodException( "'{$this->logic}' doesn't supported yet" diff --git a/core/Logic/CalculatableExpression.class.php b/core/Logic/CalculatableExpression.class.php new file mode 100644 index 0000000000..f7a2b4eeb8 --- /dev/null +++ b/core/Logic/CalculatableExpression.class.php @@ -0,0 +1,105 @@ + + * @date 20.02.14 + */ + +class CalculatableExpression extends BinaryExpression { + + public static function add($left, $right) { + return new CalculatableExpression($left, $right, self::ADD); + } + + public static function sub($left, $right) { + return new CalculatableExpression($left, $right, self::SUBSTRACT); + } + + public static function div($left, $right) { + return new CalculatableExpression($left, $right, self::DIVIDE); + } + + public static function mul($left, $right) { + return new CalculatableExpression($left, $right, self::MULTIPLY); + } + + protected function operandToValue($operand, $dataSource) { + if ($operand instanceof CalculatableExpression) { + return $operand->toValue($dataSource); + + } else if (is_string($operand)) { + if ($dataSource instanceof Form) { + return $dataSource->getValue($operand); + + } else if ($dataSource instanceof Prototyped) { + return PrototypeUtils::getValue($dataSource, $operand); + + } else if (is_array($dataSource)) { + return isset($dataSource[$operand]) ? $dataSource[$operand] : 0; + + } else { + throw new WrongArgumentException('$dataSource should be Form or Prototyped or array'); + } + + } else { + return floatval($operand); + } + } + + /** + * @param Form|Prototyped|array $dataSource + * @return float + * @throws UnsupportedMethodException + */ + public function toValue($dataSource) + { + $left = $this->operandToValue($this->getLeft(), $dataSource); + $right = $this->operandToValue($this->getRight(), $dataSource); + + switch ($this->getLogic()) { + case self::EQUALS: + return $left == $right ? 1 : 0; + + case self::NOT_EQUALS: + return $left != $right ? 1 : 0; + + case self::GREATER_THAN: + return $left > $right ? 1 : 0; + + case self::GREATER_OR_EQUALS: + return $left >= $right ? 1 : 0; + + case self::LOWER_THAN: + return $left < $right ? 1 : 0; + + case self::LOWER_OR_EQUALS: + return $left <= $right ? 1 : 0; + + case self::EXPRESSION_AND: + return $left && $right ? 1 : 0; + + case self::EXPRESSION_OR: + return $left || $right ? 1 : 0; + + case self::ADD: + return $left + $right; + + case self::SUBSTRACT: + return $left - $right; + + case self::MULTIPLY: + return $left * $right; + + case self::DIVIDE: + return $left / $right; + + case self::MOD: + return $left % $right; + + default: + throw new UnsupportedMethodException( + "'{$this->getLogic()}' doesn't supported yet" + ); + } + } +} \ No newline at end of file diff --git a/core/Logic/CallbackLogicalObject.class.php b/core/Logic/CallbackLogicalObject.class.php old mode 100644 new mode 100755 diff --git a/core/Logic/EqualsLowerExpression.class.php b/core/Logic/EqualsLowerExpression.class.php old mode 100644 new mode 100755 diff --git a/core/Logic/Expression.class.php b/core/Logic/Expression.class.php old mode 100644 new mode 100755 index 1f85332d72..bf373f0595 --- a/core/Logic/Expression.class.php +++ b/core/Logic/Expression.class.php @@ -81,7 +81,7 @@ public static function gt($field, $value) **/ public static function gtEq($field, $value) { - return new BinaryExpression( + return new BinaryExpression( $field, $value, BinaryExpression::GREATER_OR_EQUALS ); } diff --git a/core/Logic/HstoreExpression.class.php b/core/Logic/HstoreExpression.class.php old mode 100644 new mode 100755 diff --git a/core/Logic/InExpression.class.php b/core/Logic/InExpression.class.php old mode 100644 new mode 100755 index fcd1379bfd..6c4af48066 --- a/core/Logic/InExpression.class.php +++ b/core/Logic/InExpression.class.php @@ -29,6 +29,7 @@ public function __construct($left, $right, $logic) ($right instanceof Query) || ($right instanceof Criteria) || ($right instanceof MappableObject) + || ($right instanceof DialectString) || is_array($right) ); diff --git a/core/Logic/Ip4ContainsExpression.class.php b/core/Logic/Ip4ContainsExpression.class.php old mode 100644 new mode 100755 diff --git a/core/Logic/LTreeExpression.class.php b/core/Logic/LTreeExpression.class.php old mode 100644 new mode 100755 diff --git a/core/Logic/LogicalBetween.class.php b/core/Logic/LogicalBetween.class.php old mode 100644 new mode 100755 diff --git a/core/Logic/LogicalChain.class.php b/core/Logic/LogicalChain.class.php old mode 100644 new mode 100755 diff --git a/core/Logic/LogicalObject.class.php b/core/Logic/LogicalObject.class.php old mode 100644 new mode 100755 diff --git a/core/Logic/MappableObject.class.php b/core/Logic/MappableObject.class.php old mode 100644 new mode 100755 diff --git a/core/Logic/NoSQLExpression.class.php b/core/Logic/NoSQLExpression.class.php new file mode 100755 index 0000000000..1345990cc6 --- /dev/null +++ b/core/Logic/NoSQLExpression.class.php @@ -0,0 +1,377 @@ + + * @author Alex Gorbylev + * @tweaks Anton Gurov + * @date 2013.07.03 + */ +final class NoSQLExpression implements LogicalObject, MappableObject { + + const C_TYPE = 1; + const C_FIELD = 2; + const C_VALUE = 3; + + const V_LEFT = 101; + const V_RIGHT = 102; + + const EXP_EQ = 1001; + const EXP_NOT_EQ = 1002; + const EXP_GT = 1003; + const EXP_GTE = 1004; + const EXP_LT = 1005; + const EXP_LTE = 1006; + const EXP_BTW_STR = 1007; + const EXP_BTW_SFT = 1008; + const EXP_IN = 1009; + const EXP_NOT_IN = 1010; + const EXP_TYPE = 1100; + + /** + * true = объединять условия по И + * false = разделять условия по ИЛИ + * @var bool + */ + protected $unite = null; + + protected $fields = array(); + protected $conditions = array(); + +// public static function create($unite = true) { +// return new self($unite); +// } + + /** + * Создает условие типа И + * @static + * @return NoSQLExpression + */ + public static function createAnd() { + return new self(true); + } + + /** + * Создает условие типа ИЛИ + * @static + * @return NoSQLExpression + */ + public static function createOr() { + return new self(false); + } + + public function __construct($unite = true) { + $this->unite = (bool)$unite; + } + + /** + * Замена Assert::checkInteger + * @param $variable + * @return bool + */ + public static function checkComparable($variable) { + if (Assert::checkScalar($variable) || $variable instanceof MongoDate) { + return true; + } else { + return false; + } + } + + /** + * Замена Assert::IsInteger + * @param $variable + * @param $message + * @throws WrongArgumentException + */ + protected static function assertIsComparable($variable, $message = null) { + if (!self::checkComparable($variable)) { + throw new WrongArgumentException( + $message.', '.Assert::dumpArgument($variable) + ); + } + } +/// field list +//@{ + /** + * @param string $fieldName + * @return NoSQLExpression + */ + public function addField($fieldName) { + $this->fields = $fieldName; + return $this; + } +//@} + +/// condition setters +//@{ + public function addEq($field, $value) { + if (is_string($value) && Assert::checkInteger($value)) { + $dbValue = array($value, (int)$value); + } elseif (is_string($value) && Assert::checkFloat($value)) { + $dbValue = array($value, (float)$value); + } else { + $dbValue = $value; + } + + if (is_array($dbValue) /* weak typing */) { + $this->conditions[] = array( + self::C_TYPE => self::EXP_IN, + self::C_FIELD => (string)$field, + self::C_VALUE => $dbValue, + ); + } else { + $this->conditions[] = array( + self::C_TYPE => self::EXP_EQ, + self::C_FIELD => (string)$field, + self::C_VALUE => $dbValue, + ); + } + return $this; + } + + public function addNotEq($field, $value) { + $this->conditions[] = array( + self::C_TYPE => self::EXP_NOT_EQ, + self::C_FIELD => (string)$field, + self::C_VALUE => Assert::checkInteger($value) ? (int)$value : $value, + ); + return $this; + } + + public function addGt($field, $value) { + self::assertIsComparable($value); + $this->conditions[] = array( + self::C_TYPE => self::EXP_GT, + self::C_FIELD => (string)$field, + self::C_VALUE => $value, + ); + return $this; + } + + public function addGte($field, $value) { + self::assertIsComparable($value); + $this->conditions[] = array( + self::C_TYPE => self::EXP_GTE, + self::C_FIELD => (string)$field, + self::C_VALUE => $value, + ); + return $this; + } + + public function addLt($field, $value) { + self::assertIsComparable($value); + $this->conditions[] = array( + self::C_TYPE => self::EXP_LT, + self::C_FIELD => (string)$field, + self::C_VALUE => $value, + ); + return $this; + } + + public function addLte($field, $value) { + self::assertIsComparable($value); + $this->conditions[] = array( + self::C_TYPE => self::EXP_LTE, + self::C_FIELD => (string)$field, + self::C_VALUE => $value, + ); + return $this; + } + + public function addBetweenStrict($field, $left, $right) { + self::assertIsComparable($left); + self::assertIsComparable($right); + $this->conditions[] = array( + self::C_TYPE => self::EXP_BTW_STR, + self::C_FIELD => (string)$field, + self::C_VALUE => array( self::V_LEFT=>$left, self::V_RIGHT=>$right ), + ); + return $this; + } + + public function addBetweenSoft($field, $left, $right) { + self::assertIsComparable($left); + self::assertIsComparable($right); + $this->conditions[] = array( + self::C_TYPE => self::EXP_BTW_SFT, + self::C_FIELD => (string)$field, + self::C_VALUE => array( self::V_LEFT=>$left, self::V_RIGHT=>$right ), + ); + return $this; + } + + public function addIn($field, array $value) { + /*foreach($value as &$inVal) { + if( is_null($inVal) ) { + $inVal = null; + } elseif( Assert::checkInteger($inVal) ) { + $inVal = (int)$inVal; + } else { + $inVal = (string)$inVal; + } + }*/ + $this->conditions[] = array( + self::C_TYPE => self::EXP_IN, + self::C_FIELD => (string)$field, + self::C_VALUE => $value, + ); + return $this; + } + + public function addNotIn($field, $value) { + foreach($value as &$inVal) { + if(Assert::checkInteger($inVal)) { + $inVal = (int)$inVal; + } else { + $inVal = (string)$inVal; + } + } + $this->conditions[] = array( + self::C_TYPE => self::EXP_NOT_IN, + self::C_FIELD => (string)$field, + self::C_VALUE => $value, + ); + return $this; + } + + public function addType($field, $value) { + $this->conditions[] = array( + self::C_TYPE => self::EXP_TYPE, + self::C_FIELD => (string)$field, + self::C_VALUE => (int)$value, + ); + return $this; + } + +//@} + +/// helper functions +//@{ +//@} + +/// condition setters +//@{ + public function getFieldList() { + return $this->fields; + } + + /** + * @return array + */ + public function getConditions() { + return $this->conditions; + } + + public function toMongoQuery() { + if( empty($this->conditions) ) { + //throw new WrongStateException('Sorry, query conditions are empty!'); + return array(); + } + // make query + $query = array(); + foreach($this->conditions as $condition) { + switch($condition[self::C_TYPE]) { + case self::EXP_EQ: { + if( $this->unite ) { + $query[ $condition[self::C_FIELD] ] = $condition[self::C_VALUE]; + } else { + $query[] = array( $condition[self::C_FIELD] => $condition[self::C_VALUE] ); + } + + } break; + case self::EXP_NOT_EQ: { + if( $this->unite ) { + $query[ $condition[self::C_FIELD] ] = array('$ne' => $condition[self::C_VALUE]); + } else { + $query[] = array( $condition[self::C_FIELD] => array('$ne' => $condition[self::C_VALUE]) ); + } + } break; + case self::EXP_GT: { + if( $this->unite ) { + $query[ $condition[self::C_FIELD] ] = array('$gt' => $condition[self::C_VALUE]); + } else { + $query[] = array( $condition[self::C_FIELD] => array('$gt' => $condition[self::C_VALUE]) ); + } + } break; + case self::EXP_GTE: { + if( $this->unite ) { + $query[ $condition[self::C_FIELD] ] = array('$gte' => $condition[self::C_VALUE]); + } else { + $query[] = array( $condition[self::C_FIELD] => array('$gte' => $condition[self::C_VALUE]) ); + } + } break; + case self::EXP_LT: { + if( $this->unite ) { + $query[ $condition[self::C_FIELD] ] = array('$lt' => $condition[self::C_VALUE]); + } else { + $query[] = array( $condition[self::C_FIELD] => array('$lt' => $condition[self::C_VALUE]) ); + } + } break; + case self::EXP_LTE: { + if( $this->unite ) { + $query[ $condition[self::C_FIELD] ] = array('$lte' => $condition[self::C_VALUE]); + } else { + $query[] = array( $condition[self::C_FIELD] => array('$lte' => $condition[self::C_VALUE]) ); + } + } break; + case self::EXP_BTW_STR: { + if( $this->unite ) { + $query[ $condition[self::C_FIELD] ] = array('$gt' => $condition[self::C_VALUE][self::V_LEFT], '$lt' => $condition[self::C_VALUE][self::V_RIGHT]); + } else { + $query[] = array( $condition[self::C_FIELD] => array('$gt' => $condition[self::C_VALUE][self::V_LEFT], '$lt' => $condition[self::C_VALUE][self::V_RIGHT]) ); + } + } break; + case self::EXP_BTW_SFT: { + if( $this->unite ) { + $query[ $condition[self::C_FIELD] ] = array('$gte' => $condition[self::C_VALUE][self::V_LEFT], '$lte' => $condition[self::C_VALUE][self::V_RIGHT]); + } else { + $query[] = array( $condition[self::C_FIELD] => array('$gte' => $condition[self::C_VALUE][self::V_LEFT], '$lte' => $condition[self::C_VALUE][self::V_RIGHT]) ); + } + } break; + case self::EXP_IN: { + if( $this->unite ) { + $query[ $condition[self::C_FIELD] ] = array('$in' => $condition[self::C_VALUE]); + } else { + $query[] = array( $condition[self::C_FIELD] => array('$in' => $condition[self::C_VALUE]) ); + } + } break; + case self::EXP_NOT_IN: { + if( $this->unite ) { + $query[ $condition[self::C_FIELD] ] = array('$nin' => $condition[self::C_VALUE]); + } else { + $query[] = array( $condition[self::C_FIELD] => array('$nin' => $condition[self::C_VALUE]) ); + } + } break; + case self::EXP_TYPE: { + if( $this->unite ) { + $query[ $condition[self::C_FIELD] ] = array('$type' => $condition[self::C_VALUE]); + } else { + $query[] = array( $condition[self::C_FIELD] => array('$type' => $condition[self::C_VALUE]) ); + } + } break; + default: { + throw new WrongStateException( 'Sorry, I do not know how to work with you condition!' ); + } break; + } + } + if( !$this->unite ) { + $query = array('$or' => $query); + } + return $query; + } +//@} + +/// parent object function implenetation +//@{ + public function toDialectString(Dialect $dialect) { + throw new UnsupportedMethodException('NoSQLExpression does not support method "toDialectStringg"'); + } + + public function toBoolean(Form $form) { + throw new UnsupportedMethodException('NoSQLExpression does not support method "toBoolean"'); + } + + public function toMapped(ProtoDAO $dao, JoinCapableQuery $query) { + throw new UnsupportedMethodException('NoSQLExpression does not support method "toMapped"'); + } +//@} + +} diff --git a/core/Logic/PostfixUnaryExpression.class.php b/core/Logic/PostfixUnaryExpression.class.php old mode 100644 new mode 100755 diff --git a/core/Logic/PrefixUnaryExpression.class.php b/core/Logic/PrefixUnaryExpression.class.php old mode 100644 new mode 100755 diff --git a/core/Logic/SQLFunctionExpression.class.php b/core/Logic/SQLFunctionExpression.class.php new file mode 100644 index 0000000000..f3f560fe5d --- /dev/null +++ b/core/Logic/SQLFunctionExpression.class.php @@ -0,0 +1,54 @@ +arguments = $arguments; + } + + public function toDialectString(Dialect $dialect) + { + /** @var SQLFunction $sql */ + $sql = call_user_func_array('SQLFunction::create', $this->arguments); + return $sql->toDialectString( $dialect ); + } + + public function toBoolean(Form $form) + { + throw new UnsupportedMethodException(); + } + + } \ No newline at end of file diff --git a/core/NoSQL/MongoBase.class.php b/core/NoSQL/MongoBase.class.php new file mode 100755 index 0000000000..b6de8de07c --- /dev/null +++ b/core/NoSQL/MongoBase.class.php @@ -0,0 +1,767 @@ + + * @date 2012.03.27 + */ +class MongoBase extends NoSQL { + + const C_TABLE = 1001; + const C_FIELDS = 1002; + const C_QUERY = 1003; + const C_ORDER = 1004; + const C_LIMIT = 1005; + const C_SKIP = 1006; + + /** + * @var string|null + */ + protected $connectionString = null; + + /** + * @var array|null + */ + protected $connectionOptions = null; + + /** + * @var Mongo + */ + protected $link = null; + + /** + * @var MongoDB + */ + protected $db = null; + + /** + * @var int параметр "safe" ("w" в 1.3.0+) + */ + protected $writeConcern = 1; + + /** @var bool */ + protected $isRetrying = false; + + protected function reconnectAndRetry($function, $args) { + // have you tried turning it off and on again? (c) + $this->disconnect(); + sleep(1); + $this->connect(); + $this->isRetrying = true; + try { + call_user_func_array(array($this, $function), $args); + } catch (Exception $e) { + $this->isRetrying = false; + throw $e; + } + $this->isRetrying = false; + } + + /** + * @return MongoBase + * @throws NoSQLException + */ + public function connect() { + // в зависимости от версии драйвера создаем нужного клиента + $Mongo = self::getClientClass(); + + if (empty($this->connectionString)) { + $conn = + 'mongodb://' + .($this->username && $this->password ? "{$this->username}:{$this->password}@" : null) + .$this->hostname + .($this->port ? ":{$this->port}" : null); + } else { + preg_match('#(.+)/(\w+)#', $this->connectionString, $matches); + $conn = $matches[1]; + $base = $matches[2]; + $this->setBasename($base); + } + + $options = array('connect' => true, 'slaveOkay' => false); + if (!empty($this->connectionOptions)) { + $options = array_merge($options, $this->connectionOptions); + } + + if ($this->persistent) { + $options['persist'] = $this->hostname.'-'.$this->basename; + } + $readPreference = isset($options['slaveOkay']) && $options['slaveOkay']; + if( $Mongo==='MongoClient' ) { + $options['readPreference'] = $readPreference ? $Mongo::RP_SECONDARY_PREFERRED : $Mongo::RP_PRIMARY_PREFERRED; + unset($options['slaveOkay']); + } + try { + $this->link = new $Mongo($conn, $options); + $this->db = $this->link->selectDB($this->basename); + if( method_exists($Mongo, 'setReadPreference') ) { + $this->link->setReadPreference($readPreference ? $Mongo::RP_SECONDARY_PREFERRED : $Mongo::RP_PRIMARY_PREFERRED); + } else { + $this->link->setSlaveOkay($options['slaveOkay']); + } + if (isset($options['w'])) { + $this->writeConcern = $options['w']; + } + + } catch(MongoConnectionException $e) { + throw new NoSQLException( + 'can not connect to MongoBase server: '.$e->getMessage() + ); + } catch(InvalidArgumentException $e) { + throw new NoSQLException( + 'can not select DB in MongoBase: '.$e->getMessage() + ); + } + + return $this; + } + + public function switchToPrimary() { + $this->connectionOptions['slaveOkay'] = false; + $this->connectionOptions['readPreference'] = 'primary'; + $this->connect(); + } + + /** + * @return MongoBase + */ + public function disconnect() { + if( $this->isConnected() ) { + $this->link->close(); + } + $this->link = null; + $this->db = null; + + return $this; + } + + /** + * @return bool + */ + public function isConnected() { + return ($this->link instanceof Mongo && $this->link->connected); + } + + /** + * @param $connectionString + * @return MongoBase + */ + public function setConnectionString($connectionString) { + $this->connectionString = $connectionString; + return $this; + } + + /** + * @param $connectionOptions + * @return MongoBase + */ + public function setConnectionOptions($connectionOptions) { + $this->connectionOptions = $connectionOptions; + return $this; + } + + /** + * @param string $sequence + * @return MongoId + */ + public function obtainSequence($sequence) { + return new MongoId(mb_strtolower(trim($sequence))); + } + + public function selectOne($table, $key) { + $row = + $this + ->db + ->selectCollection($table) + ->findOne( array('_id' => new MongoId($key)) ); + if( is_null($row) ) { + throw new ObjectNotFoundException( 'Object with id "'.$key.'" in table "'.$table.'" not found!' ); + } + // return clean row + return $this->decodeId($row); + } + + public function selectList($table, array $keys) { + // quering + $cursor = + $this + ->db + ->selectCollection($table) + ->find( array('_id' => array('$in'=>$this->makeIdList($keys)) ) ); + // recieving objects + $rows = array(); + foreach ($cursor as $row) { + $rows[] = $this->decodeId($row); + } + // return result + return $rows; + } + + public function insert($table, array $row, $options = array()) { + $row = $this->encodeId($row); + $options = array_merge( + array('safe' => true), + $options + ); + if ($options['safe']) { + if ($this->checkVersion('1.3.0')) { + $options['w'] = $this->writeConcern; + unset($options['safe']); + } else { + $options['safe'] = $this->writeConcern; + } + } + + $isSafe = isset($options['safe']) || isset($options['w']); + + try { + $result = + $this->db + ->selectCollection($table) + ->insert($row, $options); + + if ($isSafe && is_array($result)) { + $this->checkResult($result); + } + + } catch (Exception $e) { + if ($this->isRetrying) { + if ($e instanceof MongoCursorException && $e->getCode() == 11000) { + // E11000 == duplicate key error index + // если это вылезло при повторной попытке, значит первый раз таки вставили + } else { + throw $e; + } + } elseif ($e instanceof MongoCursorTimeoutException) { + $this->reconnectAndRetry(__FUNCTION__, func_get_args()); + } else { + throw $e; + } + } + + // return clean row + return $this->decodeId($row); + } + + public function batchInsert($table, array $rows, array $options = array()) { + $options = array_merge( + array('safe' => true), + $options + ); + if ($options['safe']) { + if ($this->checkVersion('1.3.0')) { + $options['w'] = $this->writeConcern; + unset($options['safe']); + } else { + $options['safe'] = $this->writeConcern; + } + } + + $isSafe = isset($options['safe']) || isset($options['w']); + + $result = + $this->db + ->selectCollection($table) + ->batchInsert($rows, $options); + + if ($isSafe && is_array($result)) { + $this->checkResult($result); + } + + return $result; + } + + public function update($table, array $row, $options = array()) { + $row = $this->encodeId($row); + $id = isset($row['_id']) ? $row['_id'] : null; + //unset($row['_id']); + $options = array_merge( + array('safe' => true), + $options + ); + if ($options['safe']) { + if ($this->checkVersion('1.3.0')) { + $options['w'] = $this->writeConcern; + unset($options['safe']); + } else { + $options['safe'] = $this->writeConcern; + } + } + + $isSafe = isset($options['safe']) || isset($options['w']); + + if (isset($options['where'])) { + if (is_array($options['where'])) { + $where = $options['where']; + } + unset($options['where']); + + } else if ($id !== null) { + $where = array('_id' => $id); + } + + if (empty($where)) { + throw new NoSQLException('empty "where" clause for update'); + } + + try { + + $result = + $this + ->db + ->selectCollection($table) + ->update($where, $row, $options); + + if ($isSafe && is_array($result)) { + $this->checkResult($result); + if (isset($result['upserted'])) { + $upserted = $result['upserted']; + if (is_array($upserted)) { + /** + * in mongo >=2.6 with driver <1.5.3 we would get an array of ids + * @see https://jira.mongodb.org/browse/PHP-1109 + */ + $upserted = array_pop($upserted); + } + if ($upserted instanceof MongoId) { + $id = $upserted; + } + } + } + + } catch (Exception $e) { + if ($e instanceof MongoCursorTimeoutException && !$this->isRetrying) { + $this->reconnectAndRetry(__FUNCTION__, func_get_args()); + } else { + throw $e; + } + } + + $row['_id'] = $id; + // return clean row + return $this->decodeId($row); + } + + protected function checkResult($result) { + if (!isset($result['ok']) || $result['ok'] == 0) { + $code = isset($result['code']) ? $result['code'] : 0; + $message = ''; + if (isset($result['err'])) { + $message .= 'err: ' . $result['err'] . '. '; + } + if (isset($result['errmsg'])) { + $message .= 'errmsg: ' . $result['errmsg'] . '. '; + } + throw new MongoException($message, $code); + } + } + + public function deleteOne($table, $key) { + return + $this + ->db + ->selectCollection($table) + ->remove( array('_id' => $this->makeId($key)), array('justOne' => true) ); + } + + public function deleteList($table, array $keys) { + return + $this + ->db + ->selectCollection($table) + ->remove( array('_id' => array('$in' => $this->makeIdList($keys))) ); + } + + public function getPlainList($table) { + // quering + $cursor = + $this + ->db + ->selectCollection($table) + ->find(); + // recieving objects + $rows = array(); + foreach ($cursor as $row) { + $rows[] = $this->decodeId($row); + } + // return result + return $rows; + } + + public function getTotalCount($table) { + return + $this + ->db + ->selectCollection($table) + ->find(array(), array('_id')) + ->count(); + } + + public function getCountByField($table, $field, $value, Criteria $criteria = null) { + if( Assert::checkInteger($value) ) { + $value = (int)$value; + } + $options = $this->parseCriteria($criteria); + + return + $this->mongoCount($table, array($field => $value), array('_id'), $options[self::C_ORDER], $options[self::C_LIMIT], $options[self::C_SKIP]); + } + + public function getListByField($table, $field, $value, Criteria $criteria = null) { + if( Assert::checkInteger($value) ) { + $value = (int)$value; + } + $options = $this->parseCriteria($criteria); + + return + $this->mongoFind($table, array($field => $value), $options[self::C_FIELDS], $options[self::C_ORDER], $options[self::C_LIMIT], $options[self::C_SKIP]); + } + + public function getIdListByField($table, $field, $value, Criteria $criteria = null) { + if( Assert::checkInteger($value) ) { + $value = (int)$value; + } + $options = $this->parseCriteria($criteria); + + return + $this->mongoFind($table, array($field => $value), array('_id'), $options[self::C_ORDER], $options[self::C_LIMIT], $options[self::C_SKIP]); + } + + public function find($table, $query) { + return + $this->mongoFind($table, $query); + } + + public function findByCriteria(Criteria $criteria) { + $options = $this->parseCriteria($criteria); + + if( !isset($options[self::C_TABLE]) ) { + throw new NoSQLException('Can not find without table!'); + } +// if( !isset($options[self::C_QUERY]) ) { +// throw new NoSQLException('Can not find without query!'); +// } + + return + $this->mongoFind($options[self::C_TABLE], $options[self::C_QUERY], $options[self::C_FIELDS], $options[self::C_ORDER], $options[self::C_LIMIT], $options[self::C_SKIP]); + } + + public function countByCriteria(Criteria $criteria) { + $options = $this->parseCriteria($criteria); + + if( !isset($options[self::C_TABLE]) ) { + throw new NoSQLException('Can not find without table!'); + } +// if( !isset($options[self::C_QUERY]) ) { +// throw new NoSQLException('Can not find without query!'); +// } + + return + $this->mongoCount($options[self::C_TABLE], $options[self::C_QUERY], array(), $options[self::C_ORDER], $options[self::C_LIMIT], $options[self::C_SKIP]); + } + + public function deleteByCriteria(Criteria $criteria, array $options = array('safe' => true)) { + $query = $this->parseCriteria($criteria); + + if( !isset($query[self::C_TABLE]) ) { + throw new NoSQLException('Can not find without table!'); + } + + // extend options + $options = array_merge( + array('safe' => true), + $options + ); + + if ($options['safe']) { + if ($this->checkVersion('1.3.0')) { + $options['w'] = $this->writeConcern; + unset($options['safe']); + } else { + $options['safe'] = $this->writeConcern; + } + } + + $this->mongoDelete($query[self::C_TABLE], $query[self::C_QUERY], $options); + } + + /** + * @param Criteria $criteria + * @return MongoCursor + * @throws NoSQLException + */ + public function makeCursorByCriteria(Criteria $criteria) { + $options = $this->parseCriteria($criteria); + + if (!isset($options[self::C_TABLE])) { + throw new NoSQLException('Can not find without table!'); + } + + return $this->mongoMakeCursor( + $options[self::C_TABLE], + $options[self::C_QUERY], + $options[self::C_FIELDS], + $options[self::C_ORDER], + $options[self::C_LIMIT], + $options[self::C_SKIP] + ); + } + + protected function mongoFind($table, array $query, array $fields=array(), array $order=null, $limit=null, $skip=null) { + // quering + $cursor = $this->mongoMakeCursor($table, $query, $fields, $order, $limit, $skip); + // recieving objects + $rows = array(); + foreach ($cursor as $row) { + $rows[] = $this->decodeId($row); + } + // return result + return $rows; + } + + protected function mongoCount($table, array $query, array $fields=array(), array $order=null, $limit=null, $skip=null) { + // quering + $cursor = $this->mongoMakeCursor($table, $query, $fields, $order, $limit, $skip); + // fetch result + $count = $cursor->count(); + // check result + self::assertCountResult($count); + // return count + return $count; + } + + public static function assertCountResult($count) { + if (!Assert::checkInteger($count) || $count < 0) { + if (is_array($count)) { + $code = isset($count['code']) ? $count['code'] : null; + $text = isset($count['errmsg']) ? $count['errmsg'] : json_encode($count); + throw new MongoCursorException($text, $code); + } else { + throw new UnexpectedValueException($count); + } + } + } + + protected function mongoDelete($table, array $query, array $options) { + $res = $this->db->selectCollection($table)->remove($query, $options); + if (isset($res['err']) && !is_null($res['err'])) { + throw new NoSQLException($res['err']); + } + } + + /** + * @param $table + * @param array $query + * @param array $fields + * @param array $order + * @param int $limit + * @param int $skip + * @return MongoCursor + */ + protected function mongoMakeCursor($table, array $query, array $fields=array(), array $order=null, $limit=null, $skip=null) { + $cursor = + $this + ->db + ->selectCollection($table) + ->find( $query, $fields ); + if( !is_null($order) ) { + $cursor->sort( $order ); + } + if( !is_null($limit) ) { + $cursor->limit( $limit ); + } + if( !is_null($skip) ) { + $cursor->skip( $skip ); + } + return $cursor; + } + + /** + * @param string $table + * @param string $map + * @param string $reduce + * @param Criteria $criteria + * @param int $timeout + * @param array $out + * @throws NoSQLException + * @return array + */ + public function mapReduce($table, $map, $reduce, Criteria $criteria=null, $timeout=30, $out=array('inline'=>1)) { + $options = $this->parseCriteria($criteria); + + $command = array( + 'mapreduce' => $table, + 'map' => new MongoCode($map), + 'reduce' => new MongoCode($reduce), + 'out' => $out + ); + // обрабатываем критерию + if( !empty($options[self::C_QUERY]) ) { + $command['query'] = $options[self::C_QUERY]; + } + if( !empty($options[self::C_ORDER]) ) { + $command['sort'] = $options[self::C_ORDER]; + } + if( !empty($options[self::C_LIMIT]) ) { + $command['limit'] = $options[self::C_LIMIT]; + } + + $result = $this->db->command($command, array('timeout'=>$timeout*1000)); + + // обрабатываем результаты + $list = array(); + if( is_array($result) && isset($result['ok']) && $result['ok']==1 ) { + if (isset($result['results'])) { + foreach( $result['results'] as $row ) { + // prepare id + $row['id'] = $row['_id']; + unset($row['_id']); + // prepare values + foreach($row['value'] as $key=>$value) { + $row[$key] = is_bool($value) ? (int)$value : $value; + } + unset($row['value']); + + $list[ $row['id'] ] = $row; + } + } else { + $list = $result; + } + } else { + throw new NoSQLException('Error during map/reduce running'); + } + return $list; + } + + public function increment($table, array $fields, Criteria $criteria = null) { + return null; + } + +/// helper functions +//@{ + /** + * Encode ID to MongoId + * @param array $row + * @return array + */ + protected function encodeId(array $row) { + if( isset($row['id']) ) { + $row['_id'] = $this->makeId($row['id']); + } + unset($row['id']); + return $row; + } + + /** + * Decode ID from MongoId to string + * @param array $row + * @return array + */ + protected function decodeId(array $row) { + $row['id'] = (string)$row['_id']; + unset($row['_id']); + return $row; + } + + protected function makeId($key) { + return ($key instanceof MongoId) ? $key : new MongoId($key); + } + + protected function makeIdList(array $keys) { + $fields = array(); + foreach( $keys as $key ) { + //$fields[] = array( '_id'=>$this->makeId($key) ); + $fields[] = $this->makeId($key); + } + return $fields; + } + + /** + * Разбираем критерию на параметры запроса к монго + * @param Criteria $criteria + * @return array + */ + protected function parseCriteria(Criteria $criteria=null) { + $result = array(); + // парсим табличку + if( !is_null($criteria) && $criteria->getDao() ) { + $result[self::C_TABLE] = $criteria->getDao()->getTable(); + } else { + $result[self::C_TABLE] = null; + } + // парсим запросы + if( !is_null($criteria) && $criteria->getLogic()->getLogic() ) { + $logic = $criteria->getLogic()->getChain(); + $expression = array_shift($logic); + if( $expression instanceof NoSQLExpression ) { + $result[self::C_FIELDS] = $expression->getFieldList(); + $result[self::C_QUERY] = $expression->toMongoQuery(); + } else { + $result[self::C_FIELDS] = array(); + $result[self::C_QUERY] = array(); + } + } else { + $result[self::C_FIELDS] = array(); + $result[self::C_QUERY] = array(); + } + // парсим сортировку + if( !is_null($criteria) && $criteria->getOrder() ) { + /** @var $order OrderBy */ + $order = $criteria->getOrder()->getLast(); + if( $order instanceof OrderBy ) { + $result[self::C_ORDER] = array($order->getFieldName() => $order->isAsc()?1:-1); + } else { + $result[self::C_ORDER] = null; + } + } else { + $result[self::C_ORDER] = null; + } + // парсим лимит + if( !is_null($criteria) && $criteria->getLimit() ) { + $result[self::C_LIMIT] = $criteria->getLimit(); + } else { + $result[self::C_LIMIT] = null; + } + // парсим сдвиг + if( !is_null($criteria) && $criteria->getOffset() ) { + $result[self::C_SKIP] = $criteria->getOffset(); + } else { + $result[self::C_SKIP] = null; + } + // отдаем результат + return $result; + } + + /** + * Возвращает актуальное имя класса клиента + * @return string + */ + public static function getClientClass() { + try { + Assert::classExists('MongoClient'); + return 'MongoClient'; + } catch( Exception $e ) { + return 'Mongo'; + } + } + + /** + * Проверяет, что драйвер соответствует или новее версии $lowest + * @param string $lowest версия в виде "1.2.3" + * @return boolean + */ + public static function checkVersion($lowest) { + $Mongo = self::getClientClass(); + try { + $version = constant($Mongo . '::VERSION'); + } catch (BaseException $e) { + return false; + } + return version_compare($version, $lowest, '>='); + } + +//@} +} diff --git a/core/NoSQL/NoSQL.class.php b/core/NoSQL/NoSQL.class.php new file mode 100755 index 0000000000..7d1628e3f2 --- /dev/null +++ b/core/NoSQL/NoSQL.class.php @@ -0,0 +1,87 @@ +1)); + + public function getTableInfo($table) { + throw new UnsupportedMethodException('Can not execute getTableInfo in NoSQL'); + } + + public function queryRaw($queryString) { + throw new UnsupportedMethodException('Can not execute queryRaw in NoSQL'); + } + + public function queryRow(Query $query) { + throw new UnsupportedMethodException('Can not execute queryRow in NoSQL'); + } + + public function querySet(Query $query) { + throw new UnsupportedMethodException('Can not execute querySet in NoSQL'); + } + + public function queryNumRows(Query $query) { + throw new UnsupportedMethodException('Can not execute queryNumRows in NoSQL'); + } + + public function queryColumn(Query $query) { + throw new UnsupportedMethodException('Can not execute queryColumn in NoSQL'); + } + + public function queryCount(Query $query) { + throw new UnsupportedMethodException('Can not execute queryCount in NoSQL'); + } + + public function setDbEncoding() { + throw new UnsupportedMethodException('Can not set encoding in NoSQL'); + } + +} diff --git a/core/NoSQL/NoSqlDAO.class.php b/core/NoSQL/NoSqlDAO.class.php new file mode 100755 index 0000000000..88af15ed7b --- /dev/null +++ b/core/NoSQL/NoSqlDAO.class.php @@ -0,0 +1,549 @@ +getLink()->selectOne( $this->getTable(), $id )) { + $object = $this->makeNoSqlObject($row); + } else { + throw new ObjectNotFoundException( 'Object with id '.$id.' does not exist' ); + } + return $object; + } + + /** + * @param LogicalObject $logic + * @param int $expires + * @return Identifiable|Prototyped + * @throws ObjectNotFoundException|UnimplementedFeatureException|WrongArgumentException + */ + public function getByLogic(LogicalObject $logic, $expires = Cache::DO_NOT_CACHE) { + if( !($logic instanceof NoSQLExpression) ) { + throw new WrongArgumentException( '$logic should be instance of NoSQLExpression' ); + } + // quering for different NoSQL types + $rows = array(); + if( $this->getLink() instanceof MongoBase ) { + $rows = $this->getLink()->find($this->getTable(), $logic->toMongoQuery()); + } else { + throw new UnimplementedFeatureException( 'Method "getByLogic" is not implemented now for your NoSQL DB' ); + } + // processing list + if( count($rows)==0 ) { + throw new ObjectNotFoundException('Can not find object for your query'); + } else { + return $this->makeNoSqlObject( array_shift($rows) ); + } + } + + /** + * @param SelectQuery $query + * @param int $expires + * @throws UnsupportedMethodException + */ + public function getByQuery(SelectQuery $query, $expires = Cache::DO_NOT_CACHE) { + throw new UnsupportedMethodException( 'Can not execute "getByQuery" in NoSQL' ); + } + + /** + * @param SelectQuery $query + * @param int $expires + * @throws UnsupportedMethodException + */ + public function getCustom(SelectQuery $query, $expires = Cache::DO_NOT_CACHE) { + throw new UnsupportedMethodException( 'Can not execute "getCustom" in NoSQL' ); + } +//@} + +/// object's list getters +//@{ + /** + * @param array $ids + * @param int $expires + * @return array + */ + public function getListByIds(array $ids, $expires = Cache::EXPIRES_MEDIUM) { + $list = array(); + $rows = $this->getLink()->selectList( $this->getTable(), $ids ); + foreach($rows as $row) { + $list[] = $this->makeNoSqlObject($row); + } + return $list; + } + + /** + * @param SelectQuery $query + * @param int $expires + * @throws UnsupportedMethodException + */ + public function getListByQuery(SelectQuery $query, $expires = Cache::DO_NOT_CACHE) { + throw new UnsupportedMethodException( 'Can not execute "getListByQuery" in NoSQL' ); + } + + /** + * @param LogicalObject $logic + * @param int $expires + * @return array + * @throws UnimplementedFeatureException|WrongArgumentException + */ + public function getListByLogic(LogicalObject $logic, $expires = Cache::DO_NOT_CACHE) { + if( !($logic instanceof NoSQLExpression) ) { + throw new WrongArgumentException( '$logic should be instance of NoSQLExpression' ); + } + // quering for different NoSQL types + $rows = array(); + if( $this->getLink() instanceof MongoBase ) { + $rows = $this->getLink()->find($this->getTable(), $logic->toMongoQuery()); + } else { + throw new UnimplementedFeatureException( 'Method "getByLogic" is not implemented now for your NoSQL DB' ); + } + // processing list + $list = array(); + foreach($rows as $row) { + $list[] = $this->makeNoSqlObject($row); + } + return $list; + } + + /** + * @param Criteria $criteria + * @param int $expires + * @return array + */ + public function getListByCriteria(Criteria $criteria, $expires = Cache::DO_NOT_CACHE) { + $criteria->setDao( $this ); + // getting list + $list = array(); + $stack = $this->getLink()->findByCriteria($criteria); + foreach( $stack as $row ) { + $object = $this->makeNoSqlObject($row); + $list[ $object->getId() ] = $object; + } + return $list; + } + + /** + * @param Criteria $criteria + * @param int $expires + * @return int + */ + public function getCountByCriteria(Criteria $criteria, $expires = Cache::DO_NOT_CACHE) { + $criteria->setDao( $this ); + return $this->getLink()->countByCriteria($criteria); + } + + /** + * @param int $expires + * @return array + */ + public function getPlainList($expires = Cache::EXPIRES_MEDIUM) { + $list = array(); + $stack = $this->getLink()->getPlainList( $this->getTable() ); + foreach( $stack as $row ) { + $object = $this->makeNoSqlObject($row); + $list[ $object->getId() ] = $object; + } + + return $list; + } + + /** + * @param int $expires + * @return int + */ + public function getTotalCount($expires = Cache::DO_NOT_CACHE) { + return $this->getLink()->getTotalCount( $this->getTable() ); + } +//@} + + +/// custom list getters +//@{ + public function getCustomList(SelectQuery $query, $expires = Cache::DO_NOT_CACHE) { + throw new UnsupportedMethodException( 'Can not execute "getCustomList" in NoSQL' ); + } + + public function getCustomRowList(SelectQuery $query, $expires = Cache::DO_NOT_CACHE) { + throw new UnsupportedMethodException( 'Can not execute "getCustomRowList" in NoSQL' ); + } +//@} + +/// query result getters +//@{ + public function getQueryResult(SelectQuery $query, $expires = Cache::DO_NOT_CACHE) { + throw new UnsupportedMethodException( 'Can not execute "getQueryResult" in NoSQL' ); + } + + public function getNoSqlResult(Criteria $criteria, $expires = Cache::DO_NOT_CACHE) { + $criteria->setDao( $this ); + $cursor = $this->getLink()->makeCursorByCriteria($criteria); + return NoSqlResult::create() + ->setCriteria($criteria) + ->setDao($this) + ->setMongoCursor($cursor); + } +//@} + +/// some queries +//@{ + public function makeSelectHead() { + throw new UnsupportedMethodException( 'Method "makeSelectHead" is not supported in NoSQL' ); + } + + public function makeTotalCountQuery() { + throw new UnsupportedMethodException( 'Method "makeTotalCountQuery" is not supported in NoSQL' ); + } +//@} + +/// erasers +//@{ + public function drop(Identifiable $object) { + $this->assertNoSqlObject( $object ); + return $this->dropById( $object->getId() ); + } + + public function dropById($id) { + $this->runTrigger($id, 'onBeforeDrop'); + $after = $this->prepareTrigger($id, 'onAfterDrop'); + + $link = NoSqlPool::getByDao( $this ); + $result = $link->deleteOne($this->getTable(), $id); + + call_user_func($after); + + return $result; + } + + public function dropByIds(array $ids) { + $this->runTrigger($ids, 'onBeforeDrop'); + $after = $this->prepareTrigger($ids, 'onAfterDrop'); + + $link = NoSqlPool::getByDao( $this ); + + $result = $link->deleteList($this->getTable(), $ids); + + call_user_func($after); + + return $result; + } + + public function dropByCriteria(Criteria $criteria) { + $criteria->setDao($this); + $link = $this->getLink(); + if ($link instanceof MongoBase) { + /** @var $link MongoBase */ + $link->deleteByCriteria($criteria); + } else { + throw new WrongStateException('only available in MongoBase NoSqlDAO'); + } + } +//@} + +/// injects +//@{ + protected function inject( InsertOrUpdateQuery $query, Identifiable $object) { + throw new UnsupportedMethodException( 'Method "inject" is not supported in NoSQL' ); + } + + protected function doInject( InsertOrUpdateQuery $query, Identifiable $object) { + throw new UnsupportedMethodException( 'Method "doInject" is not supported in NoSQL' ); + } +//@} + +/// savers +//@{ + public function take(Identifiable $object) { + return + $object->getId() + ? $this->merge($object, true) + : $this->add($object); + } + + /** + * @param NoSqlObject[] $objectList + * @return AbstractAmqpObject|mixed|null + */ + public function multiAdd(array $objectList) { + $rows = array(); + $objectList = array_values($objectList); + foreach( $objectList as $object ) { + $this->assertNoSqlObject( $object ); + // преобразуем объект в массив для nosql + $rows[] = $object->toArray(); + } + + if( !empty($rows) ) { + $link = NoSqlPool::getByDao( $this ); + // insert + $entityList = + $link + ->batchInsert( + $this->getTable(), + $rows + ); + foreach($entityList as $key=>$entity) { + $object = $objectList[$key]; + $object->setId($entity['id']); + } + } + + // проверяем наличие ИДешек + foreach($objectList as &$object) { + if(!$object->getId()) { + unset($object); + } + } + + return $objectList; + } + + + public function addUnsafe(NoSqlObject $object) { + return $this->doAdd($object, false); + } + + public function add(Identifiable $object) { + $this->assertNoSqlObject( $object ); + return $this->doAdd($object, true); + } + + protected function doAdd(NoSqlObject $object, $safe = true) { + $this->checkNoSqlObject($object); + + $this->runTrigger($object, 'onBeforeSave'); + + $row = NoSqlPool::getByDao($this) + ->insert( + $this->getTable(), + $object->toArray(), + array('safe' => $safe) + ); + + $object->setId($row['id']); + + $this->runTrigger($object, 'onAfterSave'); + + return $object; + } + + public function saveUnsafe(NoSqlObject $object) { + return $this->doSave($object, false); + } + + public function save(Identifiable $object) { + $this->assertNoSqlObject( $object ); + return $this->doSave($object, true); + } + + protected function doSave(NoSqlObject $object, $safe = true) { + $this->checkNoSqlObject($object); + + $this->runTrigger($object, 'onBeforeSave'); + + $row = NoSqlPool::getByDao($this) + ->update( + $this->getTable(), + $object->toArray(), + array('safe' => $safe) + ); + + $this->runTrigger($object, 'onAfterSave'); + + //$object->setId($row['id']); + + return $object; + } + + public function import(Identifiable $object) { + return $this->save($object); + } + + public function merge(Identifiable $object, $cacheOnly = true) { + Assert::isNotNull($object->getId()); + + $this->checkObjectType($object); + + try { + $old = $this->getById($object->getId()); + } catch( Exception $e ) { + return $this->save($object); + } + + return $this->unite($object, $old); + } + + public function unite( Identifiable $object, Identifiable $old ) { + Assert::isNotNull($object->getId()); + + Assert::isTypelessEqual( + $object->getId(), $old->getId(), + 'cannot merge different objects' + ); + + $hasChanges = false; + + foreach ($this->getProtoClass()->getPropertyList() as $property) { + $getter = $property->getGetter(); + + if ($property->getClassName() === null) { + $changed = ($old->$getter() !== $object->$getter()); + } else { + /** + * way to skip pointless update and hack for recursive + * comparsion. + **/ + $changed = + ($old->$getter() !== $object->$getter()) + || ($old->$getter() != $object->$getter()); + } + + if ($changed) { + $hasChanges = true; + } + } + + if( $hasChanges ) { + $object = $this->save( $object ); + } + + return $object; + } +//@} + + +/// object's list getters by foreign_key +//@{ + public function getOneByField($field, $value, Criteria $criteria = null) { + if( is_null($criteria) ) { + $criteria = Criteria::create(); + } + $criteria->setLimit(1); + // get object + $list = $this->getListByField( $field, $value, $criteria ); + if( empty($list) ) { + throw new ObjectNotFoundException(); + } + return array_shift($list); + } + + public function getListByField($field, $value, Criteria $criteria = null) { + $list = array(); + $rows = $this->getLink()->getListByField( $this->getTable(), $field, $value, $criteria ); + foreach($rows as $row) { + $list[] = $this->makeNoSqlObject($row); + } + return $list; + } + + public function getIdListByField($field, $value, Criteria $criteria = null) { + $list = array(); + $rows = $this->getLink()->getIdListByField( $this->getTable(), $field, $value, $criteria ); + foreach($rows as $row) { + $list[] = $row['id']; + } + return $list; + } + + public function getCountByField($field, $value, Criteria $criteria = null) { + return $this->getLink()->getCountByField( $this->getTable(), $field, $value, $criteria ); + } +//@} + +/// map/reduce +//@{ + public function mapReduce($map, $reduce, Criteria $criteria=null, $timeout=30, $out=array('inline'=>1)) { + return $this->getLink()->mapReduce( $this->getTable(), $map, $reduce, $criteria, $timeout, $out ); + } + + public function increment($field, $value, $criteria) { + return $this->getLink()->increment( $this->getTable(), $field, $value, $criteria ); + } +//@} + + /** + * @param $object + * @throws WrongStateException + */ + protected function assertNoSqlObject( $object ) { + if( !($object instanceof NoSqlObject) ) { + throw new WrongStateException('Object must be instance of NoSqlObject'); + } + } + + /** + * @param Prototyped $object + * @return bool + * @throws NoSQLException + */ + protected function checkNoSqlObject(Prototyped $object) { + $form = Form::create(); + foreach ($object->proto()->getPropertyList() as $property) { + /** @var $property LightMetaProperty */ + if ($property->isIdentifier() || $property->getRelationId() > MetaRelation::ONE_TO_ONE) { + continue; + } + if ($property->getType() == 'scalarIdentifier') { + $form->add( + Primitive::string($property->getColumnName()) + ->setRequired($property->isRequired()) + ); + } else if ($property->getType() == 'integerIdentifier') { + $form->add( + Primitive::integer($property->getColumnName()) + ->setRequired($property->isRequired()) + ); + } else { + $form->add($property->makePrimitive($property->getColumnName())); + } + } + + $form->import(PrototypeUtils::toArray($object)); + if( $form->getErrors() ) { + throw new NoSQLException( 'Object does not have all required fields: '.var_export($form->getErrors(), true) ); + } + + return true; + } + + /** + * @param array $row + * @param null $prefix + * @throws WrongStateException + * @return Identifiable|Prototyped + */ + public function makeNoSqlObject($row, $prefix = null) { + $object = null; + try { + $object = $this->makeObject( $row, $prefix ); + } catch(Exception $e) { + throw new WrongStateException( 'Can not make object with id '.$row['id'].'. Dump: '.var_export($row, true) ); + } + return $object; + } + + /** + * @return NoSQL + */ + public function getLink() { + return NoSqlPool::me()->getLink( $this->getLinkName() ); + } + +} diff --git a/core/NoSQL/NoSqlObject.class.php b/core/NoSQL/NoSqlObject.class.php new file mode 100755 index 0000000000..563f3c2a2f --- /dev/null +++ b/core/NoSQL/NoSqlObject.class.php @@ -0,0 +1,27 @@ +getLink($dao->getLinkName()); + } + + /** + * @param NoSQL $db + * @return NoSqlPool + */ + public function setDefault(NoSQL $db) + { + $this->default = $db; + + return $this; + } + + /** + * @return NoSqlPool + **/ + public function dropDefault() + { + $this->default = null; + + return $this; + } + + /** + * @param string $name + * @param NoSQL $db + * @return NoSqlPool + * @throws WrongArgumentException + */ + public function addLink($name, NoSQL $db) + { + if (isset($this->pool[$name])) + throw new WrongArgumentException( + "already have '{$name}' link" + ); + + $this->pool[$name] = $db; + + return $this; + } + + /** + * @param string $name + * @return NoSqlPool + * @throws MissingElementException + */ + public function dropLink($name) + { + if (!isset($this->pool[$name])) + throw new MissingElementException( + "link '{$name}' not found" + ); + + unset($this->pool[$name]); + + return $this; + } + + /** + * @param string $name + * @return NoSQL + * @throws MissingElementException + */ + public function getLink($name = null) + { + /** @var $link NoSQL */ + $link = null; + + // single-NoSQL project + if (!$name) { + if (!$this->default) { + throw new MissingElementException( + 'i have no default link and requested link name is null' + ); + } + + $link = $this->default; + } elseif (isset($this->pool[$name])) { + $link = $this->pool[$name]; + } + // check if found and return + if ($link) { + if (!$link->isConnected()) + $link->connect(); + + return $link; + } + + throw new MissingElementException( + "can't find link with '{$name}' name" + ); + } + + /** + * @return NoSqlPool + */ + public function shutdown() + { + $this->default = null; + $this->pool = array(); + + return $this; + } +} +?> \ No newline at end of file diff --git a/core/NoSQL/NoSqlResult.class.php b/core/NoSQL/NoSqlResult.class.php new file mode 100644 index 0000000000..24f4129af6 --- /dev/null +++ b/core/NoSQL/NoSqlResult.class.php @@ -0,0 +1,186 @@ + + * @date 2012.07.02 + */ +class NoSqlResult extends QueryResult { + /** @var NoSqlDAO */ + protected $dao; + + /** @var MongoCursor */ + protected $mongoCursor; + + /** @var NoSqlResultList */ + protected $resultList; + + /** @var Criteria */ + protected $criteria; + + protected $count = null; + + /** + * @static + * @param MongoCursor $cursor + * @return NoSqlResult + */ + public static function create() { + return new static; + } + + + public function setCriteria(Criteria $criteria) { + $this->criteria = $criteria; + return $this; + } + + public function getCriteria() { + return $this->criteria; + } + + public function getCount() { + if ($this->count == null && $this->getMongoCursor()) { + $count = $this->getMongoCursor()->count(); + MongoBase::assertCountResult($count); + + $this->count = $count; + + /* -- плохой вариант, долго считает + // пытаемся посчитать количество записей перебором, без использования count() + // откидиываем limit и offset + $criteria = clone $this->getCriteria(); + $criteria + ->setLimit(null) + ->setOffset(null); + // находим в логике NoSQLExpression + $expression = null; + foreach ($criteria->getLogic()->getChain() as $logic) { + if ($logic instanceof NoSQLExpression) { + $expression = $logic; + } + } + + // если нашли - делаем запрос + if ($expression instanceof NoSQLExpression) { + // берем первое попавшееся в запросе поле и запрашиваем только его + foreach ($expression->toMongoQuery() as $field => $condition) { + if ($field[0] != '$') { + $expression->addField(array($field)); + break; + } + } + + $timeStart = microtime(1); + $timeMax = Config::me()->getMongoTimeout() / 2; + $this->count = 0; + $db = NoSqlPool::me()->getByDao($this->getDao()); + $cursor = $db->makeCursorByCriteria($criteria); + // пересчитываем количество выбранных записей + foreach ($cursor as $item) { + $this->count++; + + // следим за таймаутом, т.к. время запроса к базе не учитывается + // в max_execution_time + if (microtime(1) - $timeStart > $timeMax) { + throw new MongoCursorTimeoutException( + 'failed to count in ' . $timeMax . ' seconds: ' + . json_encode($expression->toMongoQuery()) + ); + } + } + + } else { + $this->count = $this->getMongoCursor()->count(); + } + */ + } + return $this->count; + } + + + /** + * @return NoSqlResultList + */ + public function getList() { + return $this->resultList; + } + + /** + * @param $resultList + * @return NoSqlResult + * @throws WrongArgumentException + */ + public function setList($resultList) { + if (!($resultList instanceof NoSqlResultList)) { + throw new WrongArgumentException('NoSqlResult accepts only NoSqlResultList in setList'); + } + $this->resultList = $resultList; + return $this; + } + + /** + * @param MongoCursor $cursor + * @return NoSqlResult + */ + public function setMongoCursor(MongoCursor $cursor) { + $this->mongoCursor = $cursor; + $this + ->setList(NoSqlResultList::create($this)) + ->setCount(null); // lazy + return $this; + } + + /** + * @return MongoCursor + */ + public function getMongoCursor() { + return $this->mongoCursor; + } + + /** + * @param NoSqlDAO $dao + * @return NoSqlResult + */ + public function setDao(NoSqlDAO $dao) { + $this->dao = $dao; + return $this; + } + + /** + * @return NoSqlDAO + */ + public function getDao() { + return $this->dao; + } + + /** + * @return null|string + */ + public function getId() { + if ($this->getMongoCursor() == null) { + return null; + } + $queryInfo = $this->getMongoCursor()->info(); + return '_result_nosql_' . sha1(json_encode($queryInfo['query'])); + } + + public function setQuery(SelectQuery $query) { + throw new UnimplementedFeatureException('NoSqlResult has no SelectQuery'); + } + + public function getQuery() { + throw new UnimplementedFeatureException('NoSqlResult has no SelectQuery'); + } + + public function setLimit($limit) { + Assert::isInteger($limit); + $this->getMongoCursor()->limit($limit); + return $this; + } + + public function setOffset($offset) { + Assert::isInteger($offset); + $this->getMongoCursor()->skip($offset); + return $this; + } +} diff --git a/core/NoSQL/NoSqlResultList.class.php b/core/NoSQL/NoSqlResultList.class.php new file mode 100644 index 0000000000..c2a965301a --- /dev/null +++ b/core/NoSQL/NoSqlResultList.class.php @@ -0,0 +1,92 @@ + + * @date 2012.07.02 + */ +class NoSqlResultList implements Iterator { + /** @var NoSqlResult */ + protected $result; + + /** + * @param NoSqlResult $result + */ + protected function __construct(NoSqlResult $result) { + $this->result = $result; + } + + /** + * @param NoSqlResult $result + * @return NoSqlResultList + */ + public static function create(NoSqlResult $result) { + return new static($result); + } + + public function getResult() { + return $this->result; + } + + public function getCursor() { + return $this->getResult()->getMongoCursor(); + } + + /** + * (PHP 5 >= 5.0.0)
+ * Return the current element + * @link http://php.net/manual/en/iterator.cur;rent.php + * @return mixed Can return any type. + */ + public function current() { + $row = $this->getCursor()->current(); + if ($row['_id'] instanceof MongoId) { + $row['id'] = (string)$row['_id']; + } else { + $row['id'] = $row['_id']; + } + unset($row['_id']); + return $this->result->getDao()->makeNoSqlObject($row); + } + + /** + * (PHP 5 >= 5.0.0)
+ * Rewind the Iterator to the first element + * @link http://php.net/manual/en/iterator.rewind.php + * @return void Any returned value is ignored. + */ + public function rewind() { + $this->getCursor()->rewind(); + } + + /** + * (PHP 5 >= 5.0.0)
+ * Checks if current position is valid + * @link http://php.net/manual/en/iterator.valid.php + * @return boolean The return value will be casted to boolean and then evaluated. + * Returns true on success or false on failure. + */ + public function valid() { + return $this->getCursor()->valid(); + } + + /** + * (PHP 5 >= 5.0.0)
+ * Return the key of the current element + * @link http://php.net/manual/en/iterator.key.php + * @return scalar scalar on success, or null on failure. + */ + public function key() { + return $this->getCursor()->key(); + } + + /** + * (PHP 5 >= 5.0.0)
+ * Move forward to next element + * @link http://php.net/manual/en/iterator.next.php + * @return void Any returned value is ignored. + */ + public function next() { + $this->getCursor()->next(); + } + +} diff --git a/core/NoSQL/RedisNoSQL.class.php b/core/NoSQL/RedisNoSQL.class.php new file mode 100644 index 0000000000..c47f5947c2 --- /dev/null +++ b/core/NoSQL/RedisNoSQL.class.php @@ -0,0 +1,371 @@ +select($db); + return $instance; + } + + public function __construct( + $host = self::DEFAULT_HOST, + $port = self::DEFAULT_PORT, + $timeout = self::DEFAULT_TIMEOUT + ) + { + $this->host = $host; + $this->port = $port; + $this->timeout = $timeout; + } + + public function __destruct() + { + if ($this->alive) { + try { + $this->redis->close(); //if pconnect - it will be ignored + } catch (RedisException $e) { + // shhhh. + } + } + } + + public function clean() + { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $this->ensureTriedToConnect(); + + try { + $this->redis->flushDB(); + $profiling + ->setInfo('clean') + ->end() + ; + } catch (RedisException $e) { + $this->alive = false; + } + + return parent::clean(); + } + + public function isAlive() + { + $this->ensureTriedToConnect(); + + try { + $this->alive = $this->redis->ping() == '+PONG'; + } catch (RedisException $e) { + $this->alive = false; + } + + return parent::isAlive(); + } + + public function append($key, $data) + { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $this->ensureTriedToConnect(); + + try { + $response = $this->redis->append($key, $data); + $profiling + ->setInfo('append ' . $key) + ->end() + ; + return $response; + } catch (RedisException $e) { + return $this->alive = false; + } + } + + public function decrement($key, $value) + { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $this->ensureTriedToConnect(); + + try { + $response = $this->redis->decrBy($key, $value); + $profiling + ->setInfo('decrement ' . $key) + ->end() + ; + return $response; + } catch (RedisException $e) { + return null; + } + } + + public function delete($key) + { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $this->ensureTriedToConnect(); + + try { + $response = $this->redis->delete($key); + $profiling + ->setInfo('delete ' . $key) + ->end() + ; + return $response; + } catch (RedisException $e) { + return $this->alive = false; + } + } + + public function get($key) + { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $this->ensureTriedToConnect(); + + try { + $response = $this->redis->get($key); + $profiling + ->setInfo('get ' . $key) + ->end() + ; + return $response; + } catch (RedisException $e) { + $this->alive = false; + + return null; + } + } + + public function keys($mask = null) + { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $this->ensureTriedToConnect(); + + if (!$mask) + $mask = '*'; + + try { + $response = $this->redis->keys($mask); + $profiling + ->setInfo('keys ' . $mask) + ->end() + ; + return $response; + } catch (RedisException $e) { + $this->alive = false; + + return null; + } + } + + public function increment($key, $value) + { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $this->ensureTriedToConnect(); + + try { + $response = $this->redis->incrBy($key, $value); + $profiling + ->setInfo('increment ' . $key) + ->end() + ; + return $response; + } catch (RedisException $e) { + return null; + } + } + + /** + * @param string $key + * + * @return RedisNoSQLList + */ + public function fetchList($key, $timeout = null) + { + $this->ensureTriedToConnect(); + + return new RedisNoSQLList($this->redis, $key, $timeout); + } + + /** + * @param string $key + * + * @return RedisNoSQLSet + */ + public function fetchSet($key) + { + throw new UnimplementedFeatureException(); + } + + /** + * @param string $key + * + * @return RedisNoSQLHash + */ + public function fetchHash($key) + { + throw new UnimplementedFeatureException(); + } + + public function info() { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $response = $this->redis->info(); + $profiling + ->setInfo('info') + ->end() + ; + return $response; + } + + public function select($db) { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $this->ensureTriedToConnect(); + + if( is_null($db) || !Assert::checkInteger($db) ) { + throw new WrongArgumentException('DB id should be an integer'); + } + $result = $this->redis->select($db); + if( !$result ) { + throw new WrongStateException('could not change db'); + } + $profiling + ->setInfo('select ' . $db) + ->end() + ; + return $result; + } + + protected function store($action, $key, $value, $expires = Cache::EXPIRES_MEDIUM) + { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $this->ensureTriedToConnect(); + + switch ($action) { + case 'set': + case 'replace': + case 'add': + try { + $result = $this->redis->set($key, $value, $expires); + $profiling + ->setInfo($action . ' ' . $key) + ->end() + ; + return $result; + } catch (RedisException $e) { + return $this->alive = false; + } + + default: + throw new UnimplementedFeatureException(); + } + } + + protected function ensureTriedToConnect() + { + if ($this->triedConnect) + return $this; + + $this->triedConnect = true; + + $this->redis = new Redis(); + + try { + $this->redis->pconnect($this->host, $this->port, $this->timeout); + $this->isAlive(); + } catch (RedisException $e) { + $this->alive = false; + } + + return $this; + } + + public function deleteList(array $keys) { + + $this->ensureTriedToConnect(); + + try { + return $this->redis->delete(implode(' ', $keys)); + } catch (RedisException $e) { + return $this->alive = false; + } + } + + public function deleteByPattern($pattern) { + + $this->ensureTriedToConnect(); + + $keys = $this->keys($pattern); + if (!$keys) + return false; + + try { + return $this->redis->delete(implode(' ', $keys)); + } catch (RedisException $e) { + return $this->alive = false; + } + } + + public function multi() { + $this->ensureTriedToConnect(); + try { + return $this->redis->multi(); + } catch (RedisException $e) { + return $this->alive = false; + } + } + + public function exec() { + $this->ensureTriedToConnect(); + try { + return $this->redis->exec(); + } catch (RedisException $e) { + return $this->alive = false; + } + } + + public function discard() { + $this->ensureTriedToConnect(); + try { + return $this->redis->discard(); + } catch (RedisException $e) { + return $this->alive = false; + } + } + +} diff --git a/core/NoSQL/RedisNoSQLList.class.php b/core/NoSQL/RedisNoSQLList.class.php new file mode 100644 index 0000000000..d6f2f802a0 --- /dev/null +++ b/core/NoSQL/RedisNoSQLList.class.php @@ -0,0 +1,189 @@ +redis = $redis; + $this->key = $key; + $this->timeout = $timeout; + } + + /** + * @param mixed $value + * @return RedisList + */ + public function append($value) + { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $this->redis->rpush($this->key, $value); + + if ($this->timeout) + $this->redis->setTimeout($this->key, $this->timeout); + + $profiling->setInfo('rpush ' . $this->key)->end(); + return $this; + } + + /** + * @param mixed $value + * @return RedisList + */ + public function prepend($value) + { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $this->redis->lpush($this->key, $value); + + if ($this->timeout) + $this->redis->setTimeout($this->key, $this->timeout); + + $profiling->setInfo('lpush ' . $this->key)->end(); + return $this; + } + + /** + * @return RedisList + */ + public function clear() + { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $this->redis->LTrim($this->key, -1, 0); + + $profiling->setInfo('LTrim ' . $this->key)->end(); + return $this; + } + + + public function count() + { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $result = $this->redis->lsize($this->key); + $profiling->setInfo('lsize ' . $this->key)->end(); + return $result; + } + + public function pop() + { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $result = $this->redis->lpop($this->key); + $profiling->setInfo('lpop ' . $this->key)->end(); + return $result; + } + + public function range($start, $length = null) + { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $end = is_null($length) + ? -1 + : $start + $length; + + $result = $this->redis->lrange($this->key, $start, $end); + $profiling->setInfo('lrange ' . $this->key . ' ' . $start . ' ' . $end)->end(); + return $result; + } + + public function get($index) + { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $result = $this->redis->lget($this->key, $index); + $profiling->setInfo('lget ' . $this->key . ' ' . $index)->end(); + return $result; + } + + public function set($index, $value) + { + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('cache', 'redis'))->begin(); + $this->redis->lset($this->key, $index, $value); + + if ($this->timeout) + $this->redis->expire($this->key, $this->timeout); + + $profiling->setInfo('lset ' . $this->key . ' ' . $index)->end(); + return $this; + } + + public function trim($start, $length = null) + { + $end = is_null($length) + ? -1 + : $start + $length - 1; + + $this->redis->ltrim($this->key, $start, $end); + } + + //region Iterator + public function current() + { + return $this->get($this->position); + } + + public function key() + { + return $this->position; + } + + public function next() + { + $this->position++; + } + + public function rewind() + { + $this->position = 0; + } + + public function valid() + { + return $this->offsetExists($this->position); + } + //endregion + + //region ArrayAccess + + public function offsetExists($offset) + { + return false !== $this->get($offset); + } + + public function offsetGet($offset) + { + return $this->get($offset); + } + + public function offsetSet($offset, $value) + { + return $this->set($offset, $value); + } + + public function offsetUnset($offset) + { + throw new UnimplementedFeatureException(); + } + + public function seek($position) { + $this->position = $position; + } + //endregion +} \ No newline at end of file diff --git a/core/OSQL/Castable.class.php b/core/OSQL/Castable.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/CombineQuery.class.php b/core/OSQL/CombineQuery.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/CreateTableQuery.class.php b/core/OSQL/CreateTableQuery.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/DBArray.class.php b/core/OSQL/DBArray.class.php new file mode 100644 index 0000000000..50851ebd36 --- /dev/null +++ b/core/OSQL/DBArray.class.php @@ -0,0 +1,61 @@ + + * @date 2013.04.15 + */ + + /** + * Container for passing array values into OSQL queries. + * + * @ingroup OSQL + * @ingroup Module + **/ + class DBArray extends DBValue { + + protected $type = null; + + /** + * @param int $value + * @return DBArray + */ + public static function create($value) + { + return new self($value); + } + + public function integers() { + $this->type = DataType::INTEGER; + return $this; + } + + public function floats() { + $this->type = DataType::REAL; + return $this; + } + + public function strings() { + $this->type = DataType::VARCHAR; + return $this; + } + + public function json() { + $this->type = DataType::JSON; + return $this; + } + + public function jsonb() { + $this->type = DataType::JSONB; + return $this; + } + + public function toDialectString(Dialect $dialect) + { + if ($this->type == DataType::JSON || $this->type == DataType::JSONB) { + return $dialect->quoteJson($this->getValue(), $this->type); + } else { + return $dialect->quoteArray($this->getValue(), $this->type); + } + } + + } \ No newline at end of file diff --git a/core/OSQL/DBBinary.class.php b/core/OSQL/DBBinary.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/DBColumn.class.php b/core/OSQL/DBColumn.class.php old mode 100644 new mode 100755 index a7a5e9c863..428d7b40be --- a/core/OSQL/DBColumn.class.php +++ b/core/OSQL/DBColumn.class.php @@ -16,18 +16,18 @@ final class DBColumn implements SQLTableName { private $type = null; private $name = null; - + private $table = null; private $default = null; - + private $reference = null; private $onUpdate = null; private $onDelete = null; - + private $primary = null; - + private $sequenced = null; - + /** * @return DBColumn **/ @@ -35,13 +35,13 @@ public static function create(DataType $type, $name) { return new self($type, $name); } - + public function __construct(DataType $type, $name) { $this->type = $type; $this->name = $name; } - + /** * @return DataType **/ @@ -49,22 +49,22 @@ public function getType() { return $this->type; } - + /** * @return DBColumn **/ public function setTable(DBTable $table) { $this->table = $table; - + return $this; } - + public function getName() { return $this->name; } - + /** * @return DBTable **/ @@ -72,37 +72,37 @@ public function getTable() { return $this->table; } - + public function isPrimaryKey() { return $this->primary; } - + /** * @return DBColumn **/ public function setPrimaryKey($primary = false) { $this->primary = true === $primary; - + return $this; } - + /** * @return DBColumn **/ public function setDefault($default) { $this->default = $default; - + return $this; } - + public function getDefault() { return $this->default; } - + /** * @throws WrongArgumentException * @return DBColumn @@ -123,14 +123,14 @@ public function setReference( || $onUpdate instanceof ForeignChangeAction ) ); - + $this->reference = $column; $this->onDelete = $onDelete; $this->onUpdate = $onUpdate; - + return $this; } - + /** * @return DBColumn **/ @@ -139,64 +139,64 @@ public function dropReference() $this->reference = null; $this->onDelete = null; $this->onUpdate = null; - + return $this; } - + public function hasReference() { return ($this->reference !== null); } - + /** * @return DBColumn **/ public function setAutoincrement($auto = false) { $this->sequenced = (true === $auto); - + return $this; } - + public function isAutoincrement() { return $this->sequenced; } - + public function toDialectString(Dialect $dialect) { $out = $dialect->quoteField($this->name).' ' .$this->type->toDialectString($dialect); - + if (null !== $this->default) { - + if ($this->type->getId() == DataType::BOOLEAN) $default = $this->default ? $dialect->literalToString(Dialect::LITERAL_TRUE) : $dialect->literalToString(Dialect::LITERAL_FALSE); else - $default = $dialect->valueToString($default); - + $default = $dialect->valueToString($this->default); + $out .= ' DEFAULT '.($default); } - + if ($this->reference) { - + $table = $this->reference->getTable()->getName(); $column = $this->reference->getName(); - + $out .= " REFERENCES {$dialect->quoteTable($table)}" ."({$dialect->quoteField($column)})"; - + if ($this->onDelete) $out .= ' ON DELETE '.$this->onDelete->toString(); - + if ($this->onUpdate) $out .= ' ON UPDATE '.$this->onUpdate->toString(); } - + return $out; } } diff --git a/core/OSQL/DBField.class.php b/core/OSQL/DBField.class.php old mode 100644 new mode 100755 index 4c8c26bf8f..be5d93df85 --- a/core/OSQL/DBField.class.php +++ b/core/OSQL/DBField.class.php @@ -15,7 +15,7 @@ * @ingroup OSQL * @ingroup Module **/ - final class DBField extends Castable implements SQLTableName + class DBField extends Castable implements SQLTableName { private $field = null; private $table = null; diff --git a/core/OSQL/DBHstoreField.class.php b/core/OSQL/DBHstoreField.class.php new file mode 100644 index 0000000000..38f841a8fe --- /dev/null +++ b/core/OSQL/DBHstoreField.class.php @@ -0,0 +1,65 @@ +setKey($key); + + return $self; + } + + public function toDialectString(Dialect $dialect) + { + $field = + ( + $this->getTable() + ? $this->getTable()->toDialectString($dialect).'.' + : null + ) + .$dialect->quoteField($this->getField()); + + if ($this->key) { + $field .= '->\'' . $this->key . '\''; + } + + $field = '(' . $field . ')'; + + return + $this->cast + ? $dialect->toCasted($field, $this->cast) + : $field; + } + + /** + * @param string $key + * @return $this + */ + public function setKey($key) { + $this->key = $key; + return $this; + } +} \ No newline at end of file diff --git a/core/OSQL/DBJsonField.class.php b/core/OSQL/DBJsonField.class.php new file mode 100644 index 0000000000..971d3aeecc --- /dev/null +++ b/core/OSQL/DBJsonField.class.php @@ -0,0 +1,65 @@ +setKey($key); + + return $self; + } + + public function toDialectString(Dialect $dialect) + { + $field = + ( + $this->getTable() + ? $this->getTable()->toDialectString($dialect).'.' + : null + ) + .$dialect->quoteField($this->getField()); + + if ($this->key) { + $field .= '->\'' . $this->key . '\''; + } + + $field = '(' . $field . ')'; + + return + $this->cast + ? $dialect->toCasted($field, $this->cast) + : $field; + } + + /** + * @param string $key + * @return $this + */ + public function setKey($key) { + $this->key = $key; + return $this; + } +} \ No newline at end of file diff --git a/core/OSQL/DBRaw.class.php b/core/OSQL/DBRaw.class.php old mode 100644 new mode 100755 index 3bd7b6c232..12869291bb --- a/core/OSQL/DBRaw.class.php +++ b/core/OSQL/DBRaw.class.php @@ -11,30 +11,31 @@ /** * Karma's destroyer. - * - * @deprecated since the begining of time - * + * * @ingroup OSQL **/ final class DBRaw implements LogicalObject { private $string = null; - + + /** + * @return DBRaw + **/ + public static function create($value) + { + return new self($value); + } + public function __construct($rawString) { - if (!defined('__I_HATE_MY_KARMA__')) - throw new UnsupportedMethodException( - 'do not use it. please.' - ); - $this->string = $rawString; } - + public function toDialectString(Dialect $dialect) { return $this->string; } - + public function toBoolean(Form $form) { throw new UnsupportedMethodException(); diff --git a/core/OSQL/DBSchema.class.php b/core/OSQL/DBSchema.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/DBTable.class.php b/core/OSQL/DBTable.class.php old mode 100644 new mode 100755 index 2f176b034d..079b65079a --- a/core/OSQL/DBTable.class.php +++ b/core/OSQL/DBTable.class.php @@ -139,7 +139,7 @@ public function toDialectString(Dialect $dialect) { return OSQL::createTable($this)->toDialectString($dialect); } - + // TODO: consider port to AlterTable class (unimplemented yet) public static function findDifferences( Dialect $dialect, @@ -150,36 +150,37 @@ public static function findDifferences( $out = array(); $head = 'ALTER TABLE '.$dialect->quoteTable($target->getName()); - + + /** @var DBColumn[] $sourceColumns */ $sourceColumns = $source->getColumns(); + /** @var DBColumn[] $targetColumns */ $targetColumns = $target->getColumns(); - + foreach ($sourceColumns as $name => $column) { if (isset($targetColumns[$name])) { if ( - $column->getType()->getId() - != $targetColumns[$name]->getType()->getId() + ($column->getType()->getId() != $targetColumns[$name]->getType()->getId() + || ($column->getType()->getSize() && $column->getType()->getSize() != $targetColumns[$name]->getType()->getSize()) + || ($column->getType()->getPrecision() && $column->getType()->getPrecision() != $targetColumns[$name]->getType()->getPrecision()) + ) + // for vertica: bigint == integer + && !($dialect instanceof VerticaDialect && ( + ($targetColumns[$name]->getType()->getId() == DataType::INTEGER + && $column->getType()->getId() == DataType::BIGINT) || + ($targetColumns[$name]->getType()->getId() == DataType::BIGINT + && $column->getType()->getId() == DataType::INTEGER) + )) ) { $targetColumn = $targetColumns[$name]; - + $out[] = $head .' ALTER COLUMN '.$dialect->quoteField($name) - .' TYPE '.$targetColumn->getType()->toString() - .( - $targetColumn->getType()->hasSize() - ? - '(' - .$targetColumn->getType()->getSize() - .( - $targetColumn->getType()->hasPrecision() - ? ', '.$targetColumn->getType()->getPrecision() - : null - ) - .')' - : null - ) - .';'; + .' TYPE '.$targetColumn->getType()->toTypeDefinition($dialect) + . ($targetColumn->getType()->in([ DataType::JSON, DataType::JSONB ]) + ? ' USING NULL' + : '') + . "; \t\t -- (has " . $column->getType()->toTypeDefinition($dialect) . ')'; } if ( @@ -213,7 +214,7 @@ public static function findDifferences( if ($column->hasReference()) { $out[] = - 'CREATE INDEX '.$dialect->quoteField($name.'_idx') + 'CREATE INDEX '.$dialect->quoteField($target->getName().'_'.$name.'_idx') .' ON '.$dialect->quoteTable($target->getName()). '('.$dialect->quoteField($name).');'; } diff --git a/core/OSQL/DBValue.class.php b/core/OSQL/DBValue.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/DataType.class.php b/core/OSQL/DataType.class.php old mode 100644 new mode 100755 index b299cb0990..fd0a0b534b --- a/core/OSQL/DataType.class.php +++ b/core/OSQL/DataType.class.php @@ -20,66 +20,89 @@ final class DataType extends Enumeration implements DialectString const INTEGER = 0x001002; const BIGINT = 0x001003; const NUMERIC = 0x001704; - + const REAL = 0x001105; const DOUBLE = 0x001106; - + const BOOLEAN = 0x000007; - + const CHAR = 0x000108; const VARCHAR = 0x000109; const TEXT = 0x00000A; - + const DATE = 0x00000B; const TIME = 0x000A0C; const TIMESTAMP = 0x000A0D; + const TIMESTAMPTZ = 0x000A0E; const INTERVAL = 0x00000F; - + const BINARY = 0x00000E; - + const IP = 0x000010; const IP_RANGE = 0x000011; - + const CIDR = 0x000012; + + const UUID = 0x000005; + const HSTORE = 0x000020; + + + const JSON = 0x000033; + const JSONB = 0x000034; + + const SET_OF_STRINGS = 0x010121; + const SET_OF_INTEGERS = 0x010022; + const SET_OF_FLOATS = 0x010023; + const HAVE_SIZE = 0x000100; const HAVE_PRECISION = 0x000200; const HAVE_SCALE = 0x000400; const HAVE_TIMEZONE = 0x000800; const CAN_BE_UNSIGNED = 0x001000; - + const ARRAY_COLUMN = 0x010000; + private $size = null; private $precision = null; private $scale = null; - + private $null = true; private $timezone = false; private $unsigned = false; - + protected $names = array( self::SMALLINT => 'SMALLINT', self::INTEGER => 'INTEGER', self::BIGINT => 'BIGINT', self::NUMERIC => 'NUMERIC', - + self::REAL => 'FLOAT', self::DOUBLE => 'DOUBLE PRECISION', - + self::BOOLEAN => 'BOOLEAN', - + + self::UUID => 'UUID', + self::HSTORE => 'HSTORE', + self::CHAR => 'CHARACTER', self::VARCHAR => 'CHARACTER VARYING', self::TEXT => 'TEXT', - + self::DATE => 'DATE', self::TIME => 'TIME', self::TIMESTAMP => 'TIMESTAMP', + self::TIMESTAMPTZ => 'TIMESTAMP', self::INTERVAL => 'INTERVAL', - + self::BINARY => 'BINARY', - + self::IP => 'IP', - self::IP_RANGE => 'IP_RANGE' + self::IP_RANGE => 'IP_RANGE', + self::CIDR => 'CIDR', + + self::SET_OF_STRINGS => 'CHARACTER VARYING', + self::SET_OF_INTEGERS => 'INTEGER', + self::SET_OF_FLOATS => 'FLOAT', ); - + /** * @return DataType **/ @@ -87,17 +110,17 @@ public static function create($id) { return new self($id); } - + public static function getAnyId() { return self::BOOLEAN; } - + public function getSize() { return $this->size; } - + /** * @throws WrongArgumentException * @return DataType @@ -105,23 +128,23 @@ public function getSize() public function setSize($size) { Assert::isInteger($size); - Assert::isTrue($this->hasSize()); - + Assert::isTrue($this->hasSize() || $this->id == self::HSTORE); + $this->size = $size; - + return $this; } - + public function hasSize() { return (bool) ($this->id & self::HAVE_SIZE); } - + public function getPrecision() { return $this->precision; } - + /** * @throws WrongArgumentException * @return DataType @@ -130,22 +153,22 @@ public function setPrecision($precision) { Assert::isInteger($precision); Assert::isTrue(($this->id & self::HAVE_PRECISION) > 0); - + $this->precision = $precision; - + return $this; } - + public function hasPrecision() { return (bool) ($this->id & self::HAVE_PRECISION); } - + public function getScale() { return $this->scale; } - + /** * @throws WrongArgumentException * @return DataType @@ -154,12 +177,12 @@ public function setScale($scale) { Assert::isInteger($scale); Assert::isTrue(($this->id & self::HAVE_SCALE) > 0); - + $this->scale = $scale; - + return $this; } - + /** * @throws WrongArgumentException * @return DataType @@ -167,32 +190,32 @@ public function setScale($scale) public function setTimezoned($zoned = false) { Assert::isTrue(($this->id & self::HAVE_TIMEZONE) > 0); - + $this->timezone = (true === $zoned); - + return $this; } - + public function isTimezoned() { return $this->timezone; } - + /** * @return DataType **/ public function setNull($isNull = false) { $this->null = ($isNull === true); - + return $this; } - + public function isNull() { return $this->null; } - + /** * @throws WrongArgumentException * @return DataType @@ -200,46 +223,51 @@ public function isNull() public function setUnsigned($unsigned = false) { Assert::isTrue(($this->id && self::CAN_BE_UNSIGNED) > 0); - + $this->unsigned = ($unsigned === true); - + return $this; } - + public function isUnsigned() { return $this->unsigned; } - - public function toDialectString(Dialect $dialect) + + public function isArrayColumn() + { + return (bool) ($this->id & self::ARRAY_COLUMN); + } + + public function toTypeDefinition(Dialect $dialect) { $out = $dialect->typeToString($this); - + if ($this->unsigned) { $out .= ' UNSIGNED'; } - + if ($this->id & self::HAVE_PRECISION) { if ($this->precision) { - + switch ($this->id) { - + case self::TIME: case self::TIMESTAMP: - + $out .= "({$this->precision})"; break; - + case self::NUMERIC: - + $out .= $this->precision ? "({$this->size}, {$this->precision})" : "({$this->size})"; break; - + default: - + throw new WrongStateException(); } } @@ -248,18 +276,28 @@ public function toDialectString(Dialect $dialect) throw new WrongStateException( "type '{$this->name}' must have size" ); - + $out .= "({$this->size})"; } - + if ($this->isArrayColumn()) { + $out .= "[]"; + } + if ($this->id & self::HAVE_TIMEZONE) $out .= $dialect->timeZone($this->timezone); + return $out; + } + + public function toDialectString(Dialect $dialect) + { + $out = $this->toTypeDefinition($dialect); + $out .= $this->null ? ' NULL' : ' NOT NULL'; - + return $out; } } diff --git a/core/OSQL/DatePart.class.php b/core/OSQL/DatePart.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/DeleteQuery.class.php b/core/OSQL/DeleteQuery.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/DialectString.class.php b/core/OSQL/DialectString.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/DropTableQuery.class.php b/core/OSQL/DropTableQuery.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/ExtractPart.class.php b/core/OSQL/ExtractPart.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/FieldGroup.class.php b/core/OSQL/FieldGroup.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/FieldTable.class.php b/core/OSQL/FieldTable.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/ForeignChangeAction.class.php b/core/OSQL/ForeignChangeAction.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/FromTable.class.php b/core/OSQL/FromTable.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/FullText.class.php b/core/OSQL/FullText.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/FullTextRank.class.php b/core/OSQL/FullTextRank.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/FullTextSearch.class.php b/core/OSQL/FullTextSearch.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/GroupBy.class.php b/core/OSQL/GroupBy.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/InsertOrUpdateQuery.class.php b/core/OSQL/InsertOrUpdateQuery.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/InsertQuery.class.php b/core/OSQL/InsertQuery.class.php old mode 100644 new mode 100755 index e37b2ba217..537b85848f --- a/core/OSQL/InsertQuery.class.php +++ b/core/OSQL/InsertQuery.class.php @@ -60,7 +60,6 @@ public function toDialectString(Dialect $dialect) } $query .= parent::toDialectString($dialect); - return $query; } @@ -83,7 +82,7 @@ protected function toDialectStringValues($query, Dialect $dialect) { $fields = array(); $values = array(); - + foreach ($this->fields as $var => $val) { $fields[] = $dialect->quoteField($var); diff --git a/core/OSQL/JoinCapableQuery.class.php b/core/OSQL/JoinCapableQuery.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/Joiner.class.php b/core/OSQL/Joiner.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/OSQL.class.php b/core/OSQL/OSQL.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/OrderBy.class.php b/core/OSQL/OrderBy.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/OrderChain.class.php b/core/OSQL/OrderChain.class.php old mode 100644 new mode 100755 index 5eab7e2ee7..eeb9c58945 --- a/core/OSQL/OrderChain.class.php +++ b/core/OSQL/OrderChain.class.php @@ -54,11 +54,24 @@ public function getLast() { return end($this->chain); } + + /** + * @return OrderBy + **/ + public function getFirst() + { + return reset($this->chain); + } public function getList() { return $this->chain; } + + public function dropChain() + { + $this->chain = array(); + } public function getCount() { @@ -71,10 +84,26 @@ public function getCount() public function toMapped(ProtoDAO $dao, JoinCapableQuery $query) { $chain = new self; - - foreach ($this->chain as $order) - $chain->add($order->toMapped($dao, $query)); - + + foreach ($this->chain as $order) { + /** + * если используется сортировка по ключ hstore, + * то необходимо добавить такое же выражение в список выбираемых столбцов, + * иначе получим ошибку от postgres + */ + if ( + $query->isDistinct() + && $dao->isTranslatedField($order->getField()) + ) { + $query->get(DBHstoreField::create( + $dao->getProtoClass()->getPropertyByName($order->getField())->getColumnName(), + $dao->getTable(), + $dao->getLanguageCode() + )); + } + $chain->add($order->toMapped($dao, $query)); + } + return $chain; } diff --git a/core/OSQL/Query.class.php b/core/OSQL/Query.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/QueryChain.class.php b/core/OSQL/QueryChain.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/QueryCombination.class.php b/core/OSQL/QueryCombination.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/QueryIdentification.class.php b/core/OSQL/QueryIdentification.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/QueryResult.class.php b/core/OSQL/QueryResult.class.php old mode 100644 new mode 100755 index dfd94d6b17..ad8d7d77cf --- a/core/OSQL/QueryResult.class.php +++ b/core/OSQL/QueryResult.class.php @@ -14,7 +14,7 @@ * * @ingroup OSQL **/ - final class QueryResult implements Identifiable + class QueryResult implements Identifiable { private $list = array(); diff --git a/core/OSQL/QuerySkeleton.class.php b/core/OSQL/QuerySkeleton.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/SQLArray.class.php b/core/OSQL/SQLArray.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/SQLBaseJoin.class.php b/core/OSQL/SQLBaseJoin.class.php old mode 100644 new mode 100755 index 00468053b6..a98916e420 --- a/core/OSQL/SQLBaseJoin.class.php +++ b/core/OSQL/SQLBaseJoin.class.php @@ -24,6 +24,10 @@ public function __construct($subject, LogicalObject $logic, $alias) $this->alias = $alias; $this->logic = $logic; } + + public static function create($subject, LogicalObject $logic, $alias) { + return new static($subject, $logic, $alias); + } public function getAlias() { diff --git a/core/OSQL/SQLChain.class.php b/core/OSQL/SQLChain.class.php old mode 100644 new mode 100755 index 6d08b88e0c..db1242ec52 --- a/core/OSQL/SQLChain.class.php +++ b/core/OSQL/SQLChain.class.php @@ -37,7 +37,23 @@ public function getChain() { return $this->chain; } - + + /** + * @param array $chain + * @return SQLChain + */ + public function setChain($chain) + { + $this->chain = $chain; + return $this; + } + + public function dropChain() + { + $this->chain = array(); + $this->logic = array(); + } + public function getLogic() { return $this->logic; diff --git a/core/OSQL/SQLFunction.class.php b/core/OSQL/SQLFunction.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/SQLJoin.class.php b/core/OSQL/SQLJoin.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/SQLLeftJoin.class.php b/core/OSQL/SQLLeftJoin.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/SQLRightJoin.class.php b/core/OSQL/SQLRightJoin.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/SQLStringConcat.class.php b/core/OSQL/SQLStringConcat.class.php new file mode 100644 index 0000000000..28cb01c60c --- /dev/null +++ b/core/OSQL/SQLStringConcat.class.php @@ -0,0 +1,107 @@ + + * @date 21.07.2014 + */ + +/** + * @ingroup OSQL + **/ +class SQLStringConcat extends Castable implements MappableObject, Aliased +{ + const OPERATOR = '||'; + + protected $args = array(); + protected $alias = null; + + /** + * @param $arg1 + * @param $arg2 + * @param ... + * @param $argN + * @return self + */ + public static function create() { + $self = new self; + $self->args = func_get_args(); + return $self; + } + + protected function __construct() {} + + /** + * @param string $alias + * @return self + */ + public function setAlias($alias) { + $this->alias = $alias; + return $this; + } + + /** + * @return string|null + */ + public function getAlias() { + return $this->alias; + } + + /** + * @param $arg + * @return self + */ + public function add($arg) { + $this->args []= $arg; + return $this; + } + + /** + * @param ProtoDAO $dao + * @param JoinCapableQuery $query + * @return self + */ + public function toMapped(ProtoDAO $dao, JoinCapableQuery $query) { + $mapped = self::create(); + + foreach ($this->args as $arg) { + if ($arg instanceof MappableObject) { + $mapped->add($arg->toMapped($dao, $query)); + } else { + $mapped->add($dao->guessAtom($arg, $query)); + } + } + + if ($this->alias) { + $mapped->setAlias($this->alias); + } + + if ($this->cast) { + $mapped->castTo($this->cast); + } + + return $mapped; + } + + public function toDialectString(Dialect $dialect) { + $strings = array(); + foreach ($this->args as $arg) { + $strings []= $dialect->toValueString($arg); + } + + $sql = '(' + . implode( + ' ' . $dialect->logicToString(self::OPERATOR) . ' ', + $strings + ) + . ')'; + + if ($this->alias) { + $sql .= ' AS ' . $dialect->quoteTable($this->alias); + } + + if ($this->cast) { + $sql = $dialect->toCasted($sql, $this->cast); + } + + return $sql; + } +} diff --git a/core/OSQL/SQLTableName.class.php b/core/OSQL/SQLTableName.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/SQLTimezoneConvert.class.php b/core/OSQL/SQLTimezoneConvert.class.php new file mode 100644 index 0000000000..2150a95130 --- /dev/null +++ b/core/OSQL/SQLTimezoneConvert.class.php @@ -0,0 +1,88 @@ + + * @date 11.10.13 + */ + +/** + * @ingroup OSQL + **/ +final class SQLTimezoneConvert extends Castable implements MappableObject, Aliased +{ + private $date = null; + private $alias = null; + private $timezone = null; + + /** + * @param $date + * @param $timezone + * @return self + */ + public static function create($date, $timezone) { + return new self($date, $timezone); + } + + public function __construct($date, $timezone) { + $this->date = $date; + $this->timezone = $timezone; + } + + /** + * @param $alias + * @return self + */ + public function setAlias($alias) { + $this->alias = $alias; + return $this; + } + + public function getAlias() { + return $this->alias; + } + + public function getDate() { + return $this->date; + } + + /** + * @param ProtoDAO $dao + * @param JoinCapableQuery $query + * @return self + */ + public function toMapped(ProtoDAO $dao, JoinCapableQuery $query) { + if ($this->date instanceof MappableObject) { + $date = $this->date->toMapped($dao, $query); + } else { + $date = $dao->guessAtom($this->date, $query); + } + + $mapped = self::create($date, $this->timezone); + if ($this->cast) { + $mapped->castTo($this->cast); + } + if ($this->alias) { + $mapped->setAlias($this->alias); + } + + return $mapped; + } + + public function toDialectString(Dialect $dialect) { + $sql = '(' + . $dialect->fieldToString($this->getDate()) + . ' AT TIME ZONE ' + . $dialect->valueToString($this->timezone) + . ')'; + + if ($this->cast) { + $sql = $dialect->toCasted($sql, $this->cast); + } + if ($this->alias) { + $sql .= ' AS ' . $dialect->quoteTable($this->alias); + } + + return $sql; + } +} diff --git a/core/OSQL/SelectField.class.php b/core/OSQL/SelectField.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/SelectQuery.class.php b/core/OSQL/SelectQuery.class.php old mode 100644 new mode 100755 index 7136be8387..dfe8747546 --- a/core/OSQL/SelectQuery.class.php +++ b/core/OSQL/SelectQuery.class.php @@ -32,34 +32,36 @@ final class SelectQuery private $group = array(); private $having = null; - + + private $criteria = null; + public function __construct() { $this->joiner = new Joiner(); $this->order = new OrderChain(); } - + public function __clone() { $this->joiner = clone $this->joiner; $this->order = clone $this->order; } - + public function hasAliasInside($alias) { return isset($this->aliases[$alias]); } - + public function getAlias() { return $this->name; } - + public function getName() { return $this->name; } - + /** * @return SelectQuery **/ @@ -67,10 +69,10 @@ public function setName($name) { $this->name = $name; $this->aliases[$name] = true; - + return $this; } - + /** * @return SelectQuery **/ @@ -79,12 +81,12 @@ public function distinct() $this->distinct = true; return $this; } - + public function isDistinct() { return $this->distinct; } - + /** * @return SelectQuery **/ @@ -93,12 +95,32 @@ public function unDistinct() $this->distinct = false; return $this; } - + public function hasJoinedTable($table) { return $this->joiner->hasJoinedTable($table); } - + + /** + * @param SQLBaseJoin $join + * @return $this + */ + public function customJoin(SQLBaseJoin $join) { + if ($join instanceof SQLLeftJoin) { + $this->joiner->leftJoin($join); + } else if ($join instanceof SQLRightJoin) { + $this->joiner->rightJoin($join); + } else if ($join instanceof SQLJoin){ + $this->joiner->join($join); + } else { + throw new UnexpectedValueException(var_export($join, true)); + } + + $this->aliases[$join->getAlias()] = true; + + return $this; + } + /** * @return SelectQuery **/ @@ -106,10 +128,10 @@ public function join($table, LogicalObject $logic, $alias = null) { $this->joiner->join(new SQLJoin($table, $logic, $alias)); $this->aliases[$alias] = true; - + return $this; } - + /** * @return SelectQuery **/ @@ -389,7 +411,7 @@ public function toDialectString(Dialect $dialect) 'SELECT '.($this->distinct ? 'DISTINCT ' : null) .implode(', ', $fieldList) .$this->joiner->toDialectString($dialect); - + // WHERE $query .= parent::toDialectString($dialect); @@ -405,17 +427,17 @@ public function toDialectString(Dialect $dialect) if ($this->having) $query .= ' HAVING '.$this->having->toDialectString($dialect); - + if ($this->order->getCount()) { $query .= ' ORDER BY '.$this->order->toDialectString($dialect); } - + if ($this->limit) $query .= ' LIMIT '.$this->limit; - + if ($this->offset) $query .= ' OFFSET '.$this->offset; - + return $query; } @@ -470,5 +492,19 @@ private function makeOrder($field, $table = null) new DBField($field, $this->getLastTable($table)) ); } + + /** + * @param $criteria + */ + public function setCriteria($criteria) { + $this->criteria = $criteria; + } + + /* + * @return Criteria|null + */ + public function getCriteria() { + return $this->criteria; + } } ?> \ No newline at end of file diff --git a/core/OSQL/TimeIntervalsGenerator.class.php b/core/OSQL/TimeIntervalsGenerator.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/TruncateQuery.class.php b/core/OSQL/TruncateQuery.class.php old mode 100644 new mode 100755 diff --git a/core/OSQL/UpdateQuery.class.php b/core/OSQL/UpdateQuery.class.php old mode 100644 new mode 100755 diff --git a/doc/AUTHORS b/doc/AUTHORS old mode 100644 new mode 100755 diff --git a/doc/ChangeLog b/doc/ChangeLog old mode 100644 new mode 100755 diff --git a/doc/CodingStyle b/doc/CodingStyle old mode 100644 new mode 100755 diff --git a/doc/Doxyfile b/doc/Doxyfile old mode 100644 new mode 100755 diff --git a/doc/FAQ b/doc/FAQ old mode 100644 new mode 100755 diff --git a/doc/FEATURES b/doc/FEATURES old mode 100644 new mode 100755 diff --git a/doc/LICENSE b/doc/LICENSE old mode 100644 new mode 100755 diff --git a/doc/OQL-BNF b/doc/OQL-BNF old mode 100644 new mode 100755 diff --git a/doc/README b/doc/README old mode 100644 new mode 100755 diff --git a/doc/ROUTERS b/doc/ROUTERS old mode 100644 new mode 100755 diff --git a/doc/THANKS b/doc/THANKS old mode 100644 new mode 100755 diff --git a/doc/TODO b/doc/TODO old mode 100644 new mode 100755 diff --git a/doc/examples/cacheSettings.php b/doc/examples/cacheSettings.php old mode 100644 new mode 100755 diff --git a/doc/examples/index.php b/doc/examples/index.php old mode 100644 new mode 100755 diff --git a/doc/examples/singleton.php b/doc/examples/singleton.php old mode 100644 new mode 100755 diff --git a/doc/examples/union.php b/doc/examples/union.php old mode 100644 new mode 100755 diff --git a/doc/onphp.doxy.php b/doc/onphp.doxy.php old mode 100644 new mode 100755 diff --git a/doc/parts/doxyHead.html b/doc/parts/doxyHead.html old mode 100644 new mode 100755 diff --git a/doc/parts/doxyHeel.html b/doc/parts/doxyHeel.html old mode 100644 new mode 100755 diff --git a/doc/patches/core_DB_Dialect.diff b/doc/patches/core_DB_Dialect.diff new file mode 100755 index 0000000000..a1b3fc6c36 --- /dev/null +++ b/doc/patches/core_DB_Dialect.diff @@ -0,0 +1,10 @@ +@@ -85,6 +85,9 @@ + if ($type->getId() == DataType::IP_RANGE) + return 'varchar(41)'; + ++ if ($type->getId() == DataType::UUID) ++ return 'varchar(36)'; ++ + return $type->getName(); + } + diff --git a/doc/patches/global-inc.diff b/doc/patches/global-inc.diff new file mode 100755 index 0000000000..b5ed717817 --- /dev/null +++ b/doc/patches/global-inc.diff @@ -0,0 +1,9 @@ +@@ -103,6 +103,8 @@ + .ONPHP_CORE_PATH.'Logic' .PATH_SEPARATOR + .ONPHP_CORE_PATH.'OSQL' .PATH_SEPARATOR + ++ .ONPHP_CORE_PATH.'NoSQL' .PATH_SEPARATOR ++ + // main framework + .ONPHP_MAIN_PATH.'Base' .PATH_SEPARATOR + diff --git a/doc/project.skel/config.inc.php.tpl b/doc/project.skel/config.inc.php.tpl old mode 100644 new mode 100755 diff --git a/doc/project.skel/config.xml b/doc/project.skel/config.xml old mode 100644 new mode 100755 diff --git a/doc/project.skel/db/schema.sql b/doc/project.skel/db/schema.sql old mode 100644 new mode 100755 diff --git a/doc/project.skel/src/admin/controllers/login.class.php b/doc/project.skel/src/admin/controllers/login.class.php old mode 100644 new mode 100755 diff --git a/doc/project.skel/src/admin/controllers/main.class.php b/doc/project.skel/src/admin/controllers/main.class.php old mode 100644 new mode 100755 diff --git a/doc/project.skel/src/admin/htdocs/index.php b/doc/project.skel/src/admin/htdocs/index.php old mode 100644 new mode 100755 diff --git a/doc/project.skel/src/admin/templates/login.tpl.html b/doc/project.skel/src/admin/templates/login.tpl.html old mode 100644 new mode 100755 diff --git a/doc/project.skel/src/admin/templates/main.tpl.html b/doc/project.skel/src/admin/templates/main.tpl.html old mode 100644 new mode 100755 diff --git a/doc/project.skel/src/classes/Auto/Business/AutoAdministrator.class.php b/doc/project.skel/src/classes/Auto/Business/AutoAdministrator.class.php old mode 100644 new mode 100755 diff --git a/doc/project.skel/src/classes/Auto/DAOs/AutoAdministratorDAO.class.php b/doc/project.skel/src/classes/Auto/DAOs/AutoAdministratorDAO.class.php old mode 100644 new mode 100755 diff --git a/doc/project.skel/src/classes/Auto/Proto/AutoProtoAdministrator.class.php b/doc/project.skel/src/classes/Auto/Proto/AutoProtoAdministrator.class.php old mode 100644 new mode 100755 diff --git a/doc/project.skel/src/classes/Auto/schema.php b/doc/project.skel/src/classes/Auto/schema.php old mode 100644 new mode 100755 diff --git a/doc/project.skel/src/classes/Business/Administrator.class.php b/doc/project.skel/src/classes/Business/Administrator.class.php old mode 100644 new mode 100755 diff --git a/doc/project.skel/src/classes/DAOs/AdministratorDAO.class.php b/doc/project.skel/src/classes/DAOs/AdministratorDAO.class.php old mode 100644 new mode 100755 diff --git a/doc/project.skel/src/classes/Flow/AuthorizationFilter.class.php b/doc/project.skel/src/classes/Flow/AuthorizationFilter.class.php old mode 100644 new mode 100755 diff --git a/doc/project.skel/src/classes/Proto/ProtoAdministrator.class.php b/doc/project.skel/src/classes/Proto/ProtoAdministrator.class.php old mode 100644 new mode 100755 diff --git a/doc/project.skel/src/user/controllers/main.class.php b/doc/project.skel/src/user/controllers/main.class.php old mode 100644 new mode 100755 diff --git a/doc/project.skel/src/user/htdocs/index.php b/doc/project.skel/src/user/htdocs/index.php old mode 100644 new mode 100755 diff --git a/doc/project.skel/src/user/templates/main.tpl.html b/doc/project.skel/src/user/templates/main.tpl.html old mode 100644 new mode 100755 diff --git a/global.inc.php.tpl b/global.inc.php.tpl old mode 100644 new mode 100755 index 7ad77d44a9..34061e533e --- a/global.inc.php.tpl +++ b/global.inc.php.tpl @@ -10,12 +10,12 @@ ***************************************************************************/ // sample system-wide configuration file - + function error2Exception($code, $string, $file, $line, $context) { throw new BaseException($string, $code); } - + /* void */ function __autoload_failed($classname, $message) { eval( @@ -24,14 +24,14 @@ .'throw new ClassNotFoundException("'.$classname.': '.$message.'");' ); } - + // file extensions define('EXT_CLASS', '.class.php'); define('EXT_TPL', '.tpl.html'); define('EXT_MOD', '.inc.php'); define('EXT_HTML', '.html'); define('EXT_UNIT', '.unit.php'); - + // overridable constant, don't forget for trailing slash // also you may consider using /dev/shm/ for cache purposes if (!defined('ONPHP_TEMP_PATH')) @@ -39,113 +39,117 @@ 'ONPHP_TEMP_PATH', sys_get_temp_dir().DIRECTORY_SEPARATOR.'onPHP'.DIRECTORY_SEPARATOR ); - + if (!defined('ONPHP_CLASS_CACHE')) define('ONPHP_CLASS_CACHE', ONPHP_TEMP_PATH); - + // classes autoload magic if (!defined('ONPHP_CLASS_CACHE_TYPE')) define('ONPHP_CLASS_CACHE_TYPE', 'classPathCache'); - + require dirname(__FILE__).DIRECTORY_SEPARATOR .'misc'.DIRECTORY_SEPARATOR .'Autoloader'.EXT_MOD; - + spl_autoload_register(array('Autoloader', ONPHP_CLASS_CACHE_TYPE), false); - + // system settings error_reporting(E_ALL | E_STRICT); set_error_handler('error2Exception', E_ALL | E_STRICT); ignore_user_abort(true); define('ONPHP_VERSION', '1.0.10.99'); - + if (!defined('ONPHP_IPC_PERMS')) define('ONPHP_IPC_PERMS', 0660); - + // paths define('ONPHP_ROOT_PATH', dirname(__FILE__).DIRECTORY_SEPARATOR); define('ONPHP_CORE_PATH', ONPHP_ROOT_PATH.'core'.DIRECTORY_SEPARATOR); define('ONPHP_MAIN_PATH', ONPHP_ROOT_PATH.'main'.DIRECTORY_SEPARATOR); define('ONPHP_META_PATH', ONPHP_ROOT_PATH.'meta'.DIRECTORY_SEPARATOR); define('ONPHP_UI_PATH', ONPHP_ROOT_PATH.'UI'.DIRECTORY_SEPARATOR); - + define('ONPHP_LIB_PATH', ONPHP_ROOT_PATH.'lib'.DIRECTORY_SEPARATOR); + if (!defined('ONPHP_META_PATH')) define( 'ONPHP_META_PATH', ONPHP_ROOT_PATH.'meta'.DIRECTORY_SEPARATOR ); - + define('ONPHP_META_CLASSES', ONPHP_META_PATH.'classes'.DIRECTORY_SEPARATOR); - + define( 'ONPHP_INCUBATOR_PATH', ONPHP_ROOT_PATH.'incubator'.DIRECTORY_SEPARATOR ); - + set_include_path( // current path get_include_path().PATH_SEPARATOR - + // core classes .ONPHP_CORE_PATH.'Base' .PATH_SEPARATOR .ONPHP_CORE_PATH.'Cache' .PATH_SEPARATOR - + .ONPHP_CORE_PATH.'DB' .PATH_SEPARATOR .ONPHP_CORE_PATH.'DB'.DIRECTORY_SEPARATOR.'Transaction'.PATH_SEPARATOR - + .ONPHP_CORE_PATH.'Exceptions' .PATH_SEPARATOR - + .ONPHP_CORE_PATH.'Form' .PATH_SEPARATOR .ONPHP_CORE_PATH.'Form'.DIRECTORY_SEPARATOR.'Filters'.PATH_SEPARATOR .ONPHP_CORE_PATH.'Form'.DIRECTORY_SEPARATOR.'Primitives'.PATH_SEPARATOR - + .ONPHP_CORE_PATH.'Logic' .PATH_SEPARATOR .ONPHP_CORE_PATH.'OSQL' .PATH_SEPARATOR - + + .ONPHP_CORE_PATH.'NoSQL' .PATH_SEPARATOR + // main framework .ONPHP_MAIN_PATH.'Base' .PATH_SEPARATOR - + .ONPHP_MAIN_PATH.'Criteria' .PATH_SEPARATOR .ONPHP_MAIN_PATH.'Criteria'.DIRECTORY_SEPARATOR.'Projections'.PATH_SEPARATOR - + .ONPHP_MAIN_PATH.'Crypto' .PATH_SEPARATOR - + .ONPHP_MAIN_PATH.'DAOs' .PATH_SEPARATOR .ONPHP_MAIN_PATH.'DAOs'.DIRECTORY_SEPARATOR.'Handlers'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'DAOs'.DIRECTORY_SEPARATOR.'Workers'.PATH_SEPARATOR - + .ONPHP_MAIN_PATH.'Flow' .PATH_SEPARATOR .ONPHP_MAIN_PATH.'SPL' .PATH_SEPARATOR - + .ONPHP_MAIN_PATH.'Net' .PATH_SEPARATOR .ONPHP_MAIN_PATH.'Net'.DIRECTORY_SEPARATOR.'Http'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'Net'.DIRECTORY_SEPARATOR.'Mail'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'Net'.DIRECTORY_SEPARATOR.'Ip'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'Net'.DIRECTORY_SEPARATOR.'Soap'.PATH_SEPARATOR - + .ONPHP_MAIN_PATH.'Math' .PATH_SEPARATOR - + .ONPHP_MAIN_PATH.'Markup' .PATH_SEPARATOR .ONPHP_MAIN_PATH.'Markup'.DIRECTORY_SEPARATOR.'Feed'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'Markup'.DIRECTORY_SEPARATOR.'Html'.PATH_SEPARATOR - + .ONPHP_MAIN_PATH.'OQL' .PATH_SEPARATOR .ONPHP_MAIN_PATH.'OQL'.DIRECTORY_SEPARATOR.'Expressions'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'OQL'.DIRECTORY_SEPARATOR.'Parsers'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'OQL'.DIRECTORY_SEPARATOR.'Statements'.PATH_SEPARATOR - + .ONPHP_MAIN_PATH.'OpenId' .PATH_SEPARATOR - + .ONPHP_MAIN_PATH.'EntityProto'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'EntityProto'.DIRECTORY_SEPARATOR.'Builders'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'EntityProto'.DIRECTORY_SEPARATOR.'Accessors'.PATH_SEPARATOR - + .ONPHP_MAIN_PATH.'UnifiedContainer'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'UI'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'UI'.DIRECTORY_SEPARATOR.'View'.PATH_SEPARATOR - + .ONPHP_MAIN_PATH.'UI'.DIRECTORY_SEPARATOR.'Widget'.PATH_SEPARATOR + .ONPHP_MAIN_PATH.'Utils' .PATH_SEPARATOR .ONPHP_MAIN_PATH.'Utils'.DIRECTORY_SEPARATOR.'TuringTest'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'Utils'.DIRECTORY_SEPARATOR.'Archivers'.PATH_SEPARATOR @@ -154,6 +158,7 @@ .ONPHP_MAIN_PATH.'Utils'.DIRECTORY_SEPARATOR.'Mobile'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'Utils'.DIRECTORY_SEPARATOR.'CommandLine'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'Utils'.DIRECTORY_SEPARATOR.'Routers'.PATH_SEPARATOR + .ONPHP_MAIN_PATH.'Utils'.DIRECTORY_SEPARATOR.'Translation'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'Utils'.DIRECTORY_SEPARATOR.'AMQP'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'Utils'.DIRECTORY_SEPARATOR.'AMQP' @@ -165,23 +170,23 @@ .ONPHP_MAIN_PATH.'Messages'.DIRECTORY_SEPARATOR.'Interface'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'Application' .PATH_SEPARATOR - + .ONPHP_MAIN_PATH.'Charts'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'Charts'.DIRECTORY_SEPARATOR.'Google'.PATH_SEPARATOR .ONPHP_MAIN_PATH.'Monitoring'.PATH_SEPARATOR - + .ONPHP_META_CLASSES.PATH_SEPARATOR - + /* .ONPHP_INCUBATOR_PATH .'classes'.DIRECTORY_SEPARATOR .'Application'.DIRECTORY_SEPARATOR.PATH_SEPARATOR - + .ONPHP_INCUBATOR_PATH .'classes'.DIRECTORY_SEPARATOR .'Application'.DIRECTORY_SEPARATOR .'Markups'.DIRECTORY_SEPARATOR.PATH_SEPARATOR - + .ONPHP_INCUBATOR_PATH .'classes'.DIRECTORY_SEPARATOR .'Application'.DIRECTORY_SEPARATOR @@ -189,7 +194,7 @@ .'Documents'.DIRECTORY_SEPARATOR.PATH_SEPARATOR */ ); - + //NOTE: disable by default //see http://pgfoundry.org/docman/view.php/1000079/117/README.txt //define('POSTGRES_IP4_ENABLED', true); diff --git a/main/Application/ApplicationUrl.class.php b/main/Application/ApplicationUrl.class.php old mode 100644 new mode 100755 diff --git a/main/Application/ScopeNavigationSchema.class.php b/main/Application/ScopeNavigationSchema.class.php old mode 100644 new mode 100755 diff --git a/main/Application/SimpleApplicationUrl.class.php b/main/Application/SimpleApplicationUrl.class.php old mode 100644 new mode 100755 diff --git a/main/Base/AbstractCollection.class.php b/main/Base/AbstractCollection.class.php old mode 100644 new mode 100755 diff --git a/main/Base/AbstractProtoClass.class.php b/main/Base/AbstractProtoClass.class.php old mode 100644 new mode 100755 index ebd9169266..1307c40e5d --- a/main/Base/AbstractProtoClass.class.php +++ b/main/Base/AbstractProtoClass.class.php @@ -17,9 +17,9 @@ abstract class AbstractProtoClass extends Singleton private $depth = 0; private $storage = array(); private $skipList = array(); - + abstract protected function makePropertyList(); - + /** * @return AbstractProtoClass **/ @@ -27,10 +27,10 @@ public function beginPrefetch() { $this->storage[++$this->depth] = array(); $this->skipList[$this->depth] = array(); - + return $this; } - + /** * @return AbstractProtoClass **/ @@ -42,45 +42,45 @@ public function skipObjectPrefetching(Identifiable $object) else ++$this->skipList[$this->depth][$object->getId()]; } - + return $this; } - + public function endPrefetch(array $objectList) { if (!$this->depth) throw new WrongStateException('prefetch mode is already off'); - + foreach ($this->storage[$this->depth] as $setter => $innerList) { Assert::isEqual( count($objectList), count($innerList) + array_sum($this->skipList[$this->depth]) ); - + $ids = array(); - + foreach ($innerList as $inner) if ($inner) $ids[] = $inner->getId(); - + // finding first available inner object foreach ($innerList as $inner) if ($inner) break; - + if (!$inner) continue; - + // put yet unmapped objects into dao's identityMap $inner->dao()->getListByIds($ids); - + $skippedMap = $this->skipList[$this->depth]; - + $i = $j = 0; - + foreach ($objectList as $object) { $objectId = $object->getId(); - + if (isset($skippedMap[$objectId])) { if ($skippedMap[$objectId] == 1) unset($skippedMap[$objectId]); @@ -89,7 +89,7 @@ public function endPrefetch(array $objectList) ++$j; continue; } - + if ($innerList[$i]) { try { // avoid dao "caching" here @@ -106,58 +106,63 @@ public function endPrefetch(array $objectList) ); } } - + ++$i; } - + Assert::isEqual( $i, count($objectList) - $j ); } - + unset($this->skipList[$this->depth], $this->storage[$this->depth--]); - + return $objectList; } - + public static function makeOnlyObject($className, $array, $prefix = null) { return self::assemblyObject(new $className, $array, $prefix); } - + public static function completeObject(Prototyped $object) { return self::fetchEncapsulants($object); } - + final public function getPropertyList() { static $lists = array(); - + $className = get_class($this); - + if (!isset($lists[$className])) { $lists[$className] = $this->makePropertyList(); } - + return $lists[$className]; } - - final public function getExpandedPropertyList($prefix = null) + + final public function getExpandedPropertyList($prefix = null, $classParent = null) { static $lists = array(); - + $className = get_class($this); - + if ($classParent) { + $className = $classParent . ':' . $className; + } + if (!isset($lists[$className])) { + $lists[$className] = array(); foreach ($this->makePropertyList() as $property) { if ($property instanceof InnerMetaProperty) { $lists[$className] = array_merge( $lists[$className], $property->getProto()->getExpandedPropertyList( - $property->getName().':' + $property->getName().':', + $className ) ); } else { @@ -170,10 +175,10 @@ final public function getExpandedPropertyList($prefix = null) } } } - + return $lists[$className]; } - + /** * @return LightMetaProperty * @throws MissingElementException @@ -182,31 +187,46 @@ public function getPropertyByName($name) { if ($property = $this->safePropertyGet($name)) return $property; - + throw new MissingElementException( "unknown property requested by name '{$name}'" ); } - + public function isPropertyExists($name) { return $this->safePropertyGet($name) !== null; } - + /** * @return Form **/ - public function makeForm($prefix = null) + public function makeForm($prefix = null, $includeManyRelations = true) { $form = Form::create(); - + + /** @var $property LightMetaProperty */ foreach ($this->getPropertyList() as $property) { + if ($this->isIgnoreWhenMakeForm($property, $includeManyRelations)) { + continue; + } $property->fillForm($form, $prefix); } - + return $form; } - + + /** + * Возвращает true, если свойство не должно быть включено в форму + * Вынесено в отдельный метод, чтобы не переопределять весь makeForm + * @param LightMetaProperty $property + * @param bool $includeManyRelations + * @return bool + */ + protected function isIgnoreWhenMakeForm(LightMetaProperty $property, $includeManyRelations = true) { + return $property->getRelationId() > MetaRelation::ONE_TO_ONE && !$includeManyRelations; + } + /** * @return InsertOrUpdateQuery **/ @@ -215,18 +235,21 @@ public function fillQuery( ) { foreach ($this->getPropertyList() as $property) { - $property->fillQuery($query, $object); + /** @var $property LightMetaProperty */ + if ($property->getColumnName()) { + $property->fillQuery($query, $object); + } } - + return $query; } - + public function getMapping() { static $mappings = array(); - + $className = get_class($this); - + if (!isset($mappings[$className])) { $mapping = array(); foreach ($this->getPropertyList() as $property) { @@ -234,10 +257,10 @@ public function getMapping() } $mappings[$className] = $mapping; } - + return $mappings[$className]; } - + public function importPrimitive( $path, Form $form, @@ -253,7 +276,7 @@ public function importPrimitive( } else { $property = $this->getPropertyByName($path); $getter = $property->getGetter(); - + if ( !$property->isFormless() && ($property->getFetchStrategyId() == FetchStrategy::LAZY) @@ -261,17 +284,17 @@ public function importPrimitive( ) { return $object; } - + $value = $object->$getter(); - + if (!$ignoreNull || ($value !== null)) { $form->importValue($prm->getName(), $value); } } - + return $object; } - + public function exportPrimitive( $path, BasePrimitive $prm, @@ -287,16 +310,16 @@ public function exportPrimitive( $property = $this->getPropertyByName($path); $setter = $property->getSetter(); $value = $prm->getValue(); - + if ( !$ignoreNull || ($value !== null) ) { if ($property->isIdentifier()) { $value = $value->getId(); } - + $dropper = $property->getDropper(); - + if ( ($value === null) && method_exists($object, $dropper) @@ -309,7 +332,7 @@ public function exportPrimitive( ) ) { $object->$dropper(); - + return $object; } elseif ( ( @@ -322,24 +345,24 @@ public function exportPrimitive( ) { if ($value === null) $value = array(); - + $getter = $property->getGetter(); $object->$getter()->setList($value); - + return $object; } - + $object->$setter($value); } } - + return $object; } - + private static function fetchEncapsulants(Prototyped $object) { $proto = $object->proto(); - + foreach ($proto->getPropertyList() as $property) { if ( $property->getRelationId() == MetaRelation::ONE_TO_ONE @@ -347,7 +370,7 @@ private static function fetchEncapsulants(Prototyped $object) ) { $getter = $property->getGetter(); $setter = $property->getSetter(); - + if (($inner = $object->$getter()) instanceof DAOConnected) { if ($proto->depth) $proto->storage[$proto->depth][$setter][] = $inner; @@ -365,11 +388,11 @@ private static function fetchEncapsulants(Prototyped $object) $proto->storage[$proto->depth][$setter][] = null; } } - + return $object; } - - private static function assemblyObject( + + final protected static function assemblyObject( Prototyped $object, $array, $prefix = null ) { @@ -377,12 +400,14 @@ private static function assemblyObject( $dao = $object->dao(); else $dao = null; - + $proto = $object->proto(); - + + try { + foreach ($proto->getPropertyList() as $property) { $setter = $property->getSetter(); - + if ($property instanceof InnerMetaProperty) { $object->$setter( $property->toValue($dao, $array, $prefix) @@ -394,21 +419,29 @@ private static function assemblyObject( == FetchStrategy::LAZY ) { $columnName = $prefix.$property->getColumnName(); - + $object-> {$setter.'Id'}($array[$columnName]); - + continue; } } - + $object->$setter($property->toValue($dao, $array, $prefix)); } } - + + } catch( Exception $e ) { +// echo '
';
+//				echo $e->getTraceAsString();
+//				echo '
'; +// die('error'); + throw $e; + } + return $object; } - + private function forwardPrimitive( $path, Form $form = null, @@ -418,13 +451,13 @@ private function forwardPrimitive( ) { list($propertyName, $path) = explode(':', $path, 2); - + $property = $this->getPropertyByName($propertyName); - + Assert::isTrue($property instanceof InnerMetaProperty); - + $getter = $property->getGetter(); - + if ($form) return $property->getProto()->importPrimitive( $path, $form, $prm, $object->$getter(), $ignoreNull @@ -434,14 +467,14 @@ private function forwardPrimitive( $path, $prm, $object->$getter(), $ignoreNull ); } - + private function safePropertyGet($name) { $list = $this->getPropertyList(); - - if (isset($list[$name])) + + if (is_string($name) && isset($list[$name])) return $list[$name]; - + return null; } } diff --git a/main/Base/BaseRange.class.php b/main/Base/BaseRange.class.php old mode 100644 new mode 100755 diff --git a/main/Base/CalendarDay.class.php b/main/Base/CalendarDay.class.php old mode 100644 new mode 100755 index b3a718951d..6923975ab3 --- a/main/Base/CalendarDay.class.php +++ b/main/Base/CalendarDay.class.php @@ -29,7 +29,10 @@ public static function create($timestamp) public function __sleep() { - return array('int', 'selected', 'outside'); + $sleep = parent::__sleep(); + $sleep[] = 'selected'; + $sleep[] = 'outside'; + return $sleep; } public function isSelected() diff --git a/main/Base/CalendarMonthWeekly.class.php b/main/Base/CalendarMonthWeekly.class.php old mode 100644 new mode 100755 diff --git a/main/Base/CalendarWeek.class.php b/main/Base/CalendarWeek.class.php old mode 100644 new mode 100755 diff --git a/main/Base/CallChain.class.php b/main/Base/CallChain.class.php old mode 100644 new mode 100755 diff --git a/main/Base/Collection.class.php b/main/Base/Collection.class.php old mode 100644 new mode 100755 diff --git a/main/Base/CollectionItem.class.php b/main/Base/CollectionItem.class.php old mode 100644 new mode 100755 diff --git a/main/Base/Comparator.class.php b/main/Base/Comparator.class.php old mode 100644 new mode 100755 diff --git a/main/Base/DateObjectComparator.class.php b/main/Base/DateObjectComparator.class.php old mode 100644 new mode 100755 diff --git a/main/Base/DateRange.class.php b/main/Base/DateRange.class.php old mode 100644 new mode 100755 diff --git a/main/Base/FloatRange.class.php b/main/Base/FloatRange.class.php old mode 100644 new mode 100755 diff --git a/main/Base/Hstore.class.php b/main/Base/Hstore.class.php old mode 100644 new mode 100755 index 48bce8690d..9896ac51ea --- a/main/Base/Hstore.class.php +++ b/main/Base/Hstore.class.php @@ -12,7 +12,7 @@ /** * @ingroup Helpers **/ - final class Hstore implements Stringable + class Hstore implements Stringable { protected $properties = array(); @@ -87,6 +87,10 @@ public function isExists($key) { return isset($this->properties[$key]); } + + public function has($key) { + return $this->isExists($key); + } /** * @return Hstore @@ -97,9 +101,16 @@ public function toValue($raw) return $this; $return = null; - eval("\$return = array({$raw});"); - $this->properties = $return; - + try { + $prepared = str_replace('$', '\\$', $raw); + if (@eval("\$return = array({$prepared});") !== false) { + $this->properties = $return; + } + + } catch (Exception $e) { + // temporary - skip errors on malformed hstore data + } + return $this; } diff --git a/main/Base/IdentifiableTree.class.php b/main/Base/IdentifiableTree.class.php old mode 100644 new mode 100755 diff --git a/main/Base/ImageType.class.php b/main/Base/ImageType.class.php old mode 100644 new mode 100755 diff --git a/main/Base/ImmutableObjectComparator.class.php b/main/Base/ImmutableObjectComparator.class.php old mode 100644 new mode 100755 diff --git a/main/Base/InnerMetaProperty.class.php b/main/Base/InnerMetaProperty.class.php old mode 100644 new mode 100755 diff --git a/main/Base/IntegerSet.class.php b/main/Base/IntegerSet.class.php old mode 100644 new mode 100755 diff --git a/main/Base/IsoCurrency.class.php b/main/Base/IsoCurrency.class.php old mode 100644 new mode 100755 diff --git a/main/Base/LightMetaProperty.class.php b/main/Base/LightMetaProperty.class.php old mode 100644 new mode 100755 index e3a9225c54..0c0fdcf9b8 --- a/main/Base/LightMetaProperty.class.php +++ b/main/Base/LightMetaProperty.class.php @@ -18,7 +18,7 @@ class LightMetaProperty implements Stringable { const UNSIGNED_FLAG = 0x1000; - + private static $limits = array( 0x0002 => array( PrimitiveInteger::SIGNED_SMALL_MIN, @@ -45,34 +45,35 @@ class LightMetaProperty implements Stringable null ) ); - + private $name = null; private $columnName = null; - + private $type = null; private $className = null; - + private $size = null; - + private $min = null; private $max = null; - + private $required = false; private $generic = false; private $inner = false; - + /// @see MetaRelation private $relationId = null; - + /// @see FetchStrategy private $strategyId = null; - + private $getter = null; private $setter = null; private $dropper = null; - + private $defaulter = null; + private $identifier = null; - + /** * @return LightMetaProperty **/ @@ -80,7 +81,7 @@ public static function create() { return new self; } - + /** * must by in sync with InnerMetaProperty::make() * @@ -93,26 +94,29 @@ public static function fill( ) { $property->name = $name; - + $methodSuffix = ucfirst($name); $property->getter = 'get'.$methodSuffix; $property->setter = 'set'.$methodSuffix; $property->dropper = 'drop'.$methodSuffix; - + $property->defaulter = 'getDefault'.$methodSuffix; + if ($columnName) $property->columnName = $columnName; - else + else if ($columnName === null) $property->columnName = $name; - + $property->type = $type; $property->className = $className; - + if ($size) { if ( ($type == 'integer') || ($type == 'identifier') // obsoleted || ($type == 'integerIdentifier') || ($type == 'enumeration') + || ($type == 'enum') + || ($type == 'registry') ) { $property->min = self::$limits[$size][0]; $property->max = self::$limits[$size][1]; @@ -121,147 +125,153 @@ public static function fill( } elseif ($type != 'float') { // string $property->max = $size; } - + $property->size = $size; } - + $property->required = $required; $property->generic = $generic; $property->inner = $inner; - + $property->relationId = $relationId; $property->strategyId = $strategyId; - + $property->identifier = $generic && $required && ( ($type == 'identifier') // obsoleted || ($type == 'integerIdentifier') || ($type == 'scalarIdentifier') + || ($type == 'uuidIdentifier') ); - + return $property; } - + public function getName() { return $this->name; } - + public function getColumnName() { return $this->columnName; } - + public function getGetter() { return $this->getter; } - + public function getSetter() { return $this->setter; } - + public function getDropper() { return $this->dropper; } - + + public function getDefaulter() + { + return $this->defaulter; + } + /** * @return LightMetaProperty **/ public function setColumnName($name) { $this->columnName = $name; - + return $this; } - + public function getClassName() { return $this->className; } - + public function getMin() { return $this->min; } - + public function getMax() { return $this->max; } - + public function getType() { return $this->type; } - + public function isRequired() { return $this->required; } - + /** * @return LightMetaProperty **/ public function setRequired($yrly) { $this->required = $yrly; - + return $this; } - + public function isGenericType() { return $this->generic; } - + public function isInner() { return $this->inner; } - + public function getRelationId() { return $this->relationId; } - + public function getFetchStrategyId() { return $this->strategyId; } - + /** * @return LightMetaProperty **/ public function setFetchStrategy(FetchStrategy $strategy) { $this->strategyId = $strategy->getId(); - + return $this; } - + /** * @return LightMetaProperty **/ public function dropFetchStrategy() { $this->strategyId = null; - + return $this; } - + public function getContainerName($holderName) { return $holderName.ucfirst($this->getName()).'DAO'; } - + public function isBuildable($array, $prefix = null) { $column = $prefix.$this->columnName; $exists = isset($array[$column]); - + if ( $this->relationId || $this->generic @@ -272,7 +282,7 @@ public function isBuildable($array, $prefix = null) && !$this->generic ) return false; - + if ($this->required) { Assert::isTrue( $exists, @@ -282,38 +292,51 @@ public function isBuildable($array, $prefix = null) return false; } } - + return true; } - + /** * @return BasePrimitive **/ public function makePrimitive($name) { - $prm = - call_user_func( - array('Primitive', $this->type), - $name - ); - + + if ($this->isTranslated()) { + $prm = + call_user_func( + array('Primitive', 'string'), + $name + ); + } else { + $prm = + call_user_func( + array('Primitive', $this->type), + $name + ); + } + if (null !== ($min = $this->getMin())) $prm->setMin($min); - + if (null !== ($max = $this->getMax())) $prm->setMax($max); - + if ($prm instanceof IdentifiablePrimitive) $prm->of($this->className); - + if ($this->required) $prm->required(); - + return $prm; } - + public function fillMapping(array $mapping) { + if (empty($this->columnName)) { + return $mapping; + } + if ( !$this->relationId || ( @@ -326,10 +349,10 @@ public function fillMapping(array $mapping) ) { $mapping[$this->name] = $this->columnName; } - + return $mapping; } - + /** * @return Form **/ @@ -339,7 +362,7 @@ public function fillForm(Form $form, $prefix = null) $this->makePrimitive($prefix.$this->name) ); } - + /** * @return InsertOrUpdateQuery **/ @@ -369,29 +392,71 @@ public function fillQuery( } $value = $object->{$getter}(); - + if ($this->type == 'binary') { $query->set($this->columnName, new DBBinary($value)); + } elseif($this->type == 'arrayOfIntegers') { + $query->set($this->columnName, DBArray::create($value)->integers()); + } elseif($this->type == 'arrayOfFloats') { + $query->set($this->columnName, DBArray::create($value)->floats()); + } elseif($this->type == 'arrayOfStrings') { + $query->set($this->columnName, DBArray::create($value)->strings()); + } elseif($this->type == 'json' ) { + $query->set($this->columnName, DBArray::create($value)->json()); + } elseif($this->type == 'jsonb' ) { + $query->set($this->columnName, DBArray::create($value)->jsonb()); } else { $query->lazySet($this->columnName, $value); } } - + return $query; } - + public function toValue(ProtoDAO $dao = null, $array, $prefix = null) { $raw = $array[$prefix.$this->columnName]; - + if ($this->type == 'binary') { return DBPool::getByDao($dao)->getDialect()->unquoteBinary($raw); } - + if ($this->className == 'HttpUrl') { return HttpUrl::create()->parse($raw); } - + + if($this->type == 'set') { + // MongoDB driver compatibility + if (is_array($raw)) { + return $raw; + } else { + throw new WrongArgumentException('raw data is not array!'); + } + } + + if (strpos($this->type, 'arrayOf') !== false) { + // PgSQL driver compatibility + $matches = array(); + if ($this->type == 'arrayOfIntegers') { + $mappingFunction = 'intval'; + } else if ($this->type == 'arrayOfFloats') { + $mappingFunction = 'doubleval'; + } else if ($this->type == 'arrayOfStrings') { + $mappingFunction = 'stripslashes'; + } else { + throw new WrongArgumentException('unknown array type'); + } + if (preg_match('/^{(.*)}$/', $raw, $matches)) { + return array_map($mappingFunction, str_getcsv($matches[1])); + } else { + throw new WrongArgumentException('raw data is not compatible with ' . $this->type); + } + } + + if ($this->type == 'json' || $this->type == 'jsonb') { + return json_decode($raw, true); //associative array insteaFd of object + } + if ( !$this->identifier && $this->generic @@ -404,20 +469,24 @@ public function toValue(ProtoDAO $dao = null, $array, $prefix = null) ) { // BOVM: prevents segfault on >=php-5.2.5 Assert::classExists($this->className); - - if (!is_subclass_of($this->className, 'Enumeration')) { + + if ( + !is_subclass_of($this->className, 'Enumeration') + && !is_subclass_of($this->className, 'Enum') + && !is_subclass_of($this->className, 'Registry') + ) { $remoteDao = call_user_func(array($this->className, 'dao')); - + $joinPrefix = $remoteDao->getJoinPrefix( $this->columnName, $prefix ); - + $joined = ( ($this->strategyId == FetchStrategy::JOIN) || isset($array[$joinPrefix.$remoteDao->getIdName()]) ); - + if ($joined) { return $remoteDao->makeObject($array, $joinPrefix); } else { @@ -425,28 +494,28 @@ public function toValue(ProtoDAO $dao = null, $array, $prefix = null) // by AbstractProtoClass::fetchEncapsulants $object = new $this->className; $object->setId($raw); - + return $object; } } else { return new $this->className($raw); } } - + // veeeeery "special" handling, by tradition. // MySQL returns 0/1, others - t/f if ($this->type == 'boolean') { return (bool) strtr($raw, array('f' => null)); } - + return $raw; } - + public function isIdentifier() { return $this->identifier; } - + final public function toString() { return @@ -503,11 +572,22 @@ final public function toString() ) .')'; } - + public function isFormless() { // NOTE: enum here formless types - return ($this->type == 'enumeration'); + return in_array( + $this->type, + array( + 'enumeration', + 'enum', + 'registry', + ) + ); } + + public function isTranslated() { + return 'TranslatedStore' == $this->className; + } } ?> \ No newline at end of file diff --git a/main/Base/MimeType.class.php b/main/Base/MimeType.class.php new file mode 100644 index 0000000000..b9a678435c --- /dev/null +++ b/main/Base/MimeType.class.php @@ -0,0 +1,860 @@ + 'application/andrew-inset', + 2 => 'application/annodex', + 3 => 'application/atom+xml', + 4 => 'application/atomcat+xml', + 5 => 'application/atomserv+xml', + 6 => 'application/bbolin', + 7 => 'application/cap', + 8 => 'application/cu-seeme', + 9 => 'application/davmount+xml', + 10 => 'application/dsptype', + 11 => 'application/ecmascript', + 12 => 'application/futuresplash', + 13 => 'application/hta', + 14 => 'application/java-archive', + 15 => 'application/java-serialized-object', + 16 => 'application/java-vm', + 17 => 'application/javascript', + 18 => 'application/m3g', + 19 => 'application/mac-binhex40', + 20 => 'application/mac-compactpro', + 21 => 'application/mathematica', + 22 => 'application/msaccess', + 23 => 'application/msword', + 24 => 'application/mxf', + 25 => 'application/octet-stream', + 26 => 'application/oda', + 27 => 'application/ogg', + 28 => 'application/pdf', + 29 => 'application/pgp-keys', + 30 => 'application/pgp-signature', + 31 => 'application/pics-rules', + 32 => 'application/postscript', + 33 => 'application/rar', + 34 => 'application/rdf+xml', + 35 => 'application/rss+xml', + 36 => 'application/rtf', + 37 => 'application/smil', + 38 => 'application/xhtml+xml', + 39 => 'application/xml', + 40 => 'application/xspf+xml', + 41 => 'application/zip', + 42 => 'application/vnd.android.package-archive', + 43 => 'application/vnd.cinderella', + 44 => 'application/vnd.google-earth.kml+xml', + 45 => 'application/vnd.google-earth.kmz', + 46 => 'application/vnd.mozilla.xul+xml', + 47 => 'application/vnd.ms-excel', + 48 => 'application/vnd.ms-pki.seccat', + 49 => 'application/vnd.ms-pki.stl', + 50 => 'application/vnd.ms-powerpoint', + 51 => 'application/vnd.oasis.opendocument.chart', + 52 => 'application/vnd.oasis.opendocument.database', + 53 => 'application/vnd.oasis.opendocument.formula', + 54 => 'application/vnd.oasis.opendocument.graphics', + 55 => 'application/vnd.oasis.opendocument.graphics-template', + 56 => 'application/vnd.oasis.opendocument.image', + 57 => 'application/vnd.oasis.opendocument.presentation', + 58 => 'application/vnd.oasis.opendocument.presentation-template', + 59 => 'application/vnd.oasis.opendocument.spreadsheet', + 60 => 'application/vnd.oasis.opendocument.spreadsheet-template', + 61 => 'application/vnd.oasis.opendocument.text', + 62 => 'application/vnd.oasis.opendocument.text-master', + 63 => 'application/vnd.oasis.opendocument.text-template', + 64 => 'application/vnd.oasis.opendocument.text-web', + 65 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 66 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 67 => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 68 => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 69 => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 70 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 71 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 72 => 'application/vnd.rim.cod', + 73 => 'application/vnd.smaf', + 74 => 'application/vnd.stardivision.calc', + 75 => 'application/vnd.stardivision.chart', + 76 => 'application/vnd.stardivision.draw', + 77 => 'application/vnd.stardivision.impress', + 78 => 'application/vnd.stardivision.math', + 79 => 'application/vnd.stardivision.writer', + 80 => 'application/vnd.stardivision.writer-global', + 81 => 'application/vnd.sun.xml.calc', + 82 => 'application/vnd.sun.xml.calc.template', + 83 => 'application/vnd.sun.xml.draw', + 84 => 'application/vnd.sun.xml.draw.template', + 85 => 'application/vnd.sun.xml.impress', + 86 => 'application/vnd.sun.xml.impress.template', + 87 => 'application/vnd.sun.xml.math', + 88 => 'application/vnd.sun.xml.writer', + 89 => 'application/vnd.sun.xml.writer.global', + 90 => 'application/vnd.sun.xml.writer.template', + 91 => 'application/vnd.symbian.install', + 92 => 'application/vnd.visio', + 93 => 'application/vnd.wap.wbxml', + 94 => 'application/vnd.wap.wmlc', + 95 => 'application/vnd.wap.wmlscriptc', + 96 => 'application/vnd.wordperfect', + 97 => 'application/vnd.wordperfect5.1', + 98 => 'application/x-123', + 99 => 'application/x-7z-compressed', + 100 => 'application/x-abiword', + 101 => 'application/x-apple-diskimage', + 102 => 'application/x-bcpio', + 103 => 'application/x-bittorrent', + 104 => 'application/x-cab', + 105 => 'application/x-cbr', + 106 => 'application/x-cbz', + 107 => 'application/x-cdf', + 108 => 'application/x-cdlink', + 109 => 'application/x-chess-pgn', + 110 => 'application/x-cpio', + 111 => 'application/x-csh', + 112 => 'application/x-debian-package', + 113 => 'application/x-director', + 114 => 'application/x-dms', + 115 => 'application/x-doom', + 116 => 'application/x-dvi', + 117 => 'application/x-httpd-eruby', + 118 => 'application/x-font', + 119 => 'application/x-freemind', + 120 => 'application/x-futuresplash', + 121 => 'application/x-gnumeric', + 122 => 'application/x-go-sgf', + 123 => 'application/x-graphing-calculator', + 124 => 'application/x-gtar', + 125 => 'application/x-hdf', + 126 => 'application/x-httpd-php', + 127 => 'application/x-httpd-php-source', + 128 => 'application/x-httpd-php3', + 129 => 'application/x-httpd-php3-preprocessed', + 130 => 'application/x-httpd-php4', + 131 => 'application/x-httpd-php5', + 132 => 'application/x-ica', + 133 => 'application/x-info', + 134 => 'application/x-internet-signup', + 135 => 'application/x-iphone', + 136 => 'application/x-iso9660-image', + 137 => 'application/x-jam', + 138 => 'application/x-java-jnlp-file', + 139 => 'application/x-jmol', + 140 => 'application/x-kchart', + 141 => 'application/x-killustrator', + 142 => 'application/x-koan', + 143 => 'application/x-kpresenter', + 144 => 'application/x-kspread', + 145 => 'application/x-kword', + 146 => 'application/x-latex', + 147 => 'application/x-lha', + 148 => 'application/x-lyx', + 149 => 'application/x-lzh', + 150 => 'application/x-lzx', + 151 => 'application/x-maker', + 152 => 'application/x-mif', + 153 => 'application/x-ms-wmd', + 154 => 'application/x-ms-wmz', + 155 => 'application/x-msdos-program', + 156 => 'application/x-msi', + 157 => 'application/x-netcdf', + 158 => 'application/x-ns-proxy-autoconfig', + 159 => 'application/x-nwc', + 160 => 'application/x-object', + 161 => 'application/x-oz-application', + 162 => 'application/x-pkcs7-certreqresp', + 163 => 'application/x-pkcs7-crl', + 164 => 'application/x-python-code', + 165 => 'application/x-qgis', + 166 => 'application/x-quicktimeplayer', + 167 => 'application/x-redhat-package-manager', + 168 => 'application/x-ruby', + 169 => 'application/x-sh', + 170 => 'application/x-shar', + 171 => 'application/x-shockwave-flash', + 172 => 'application/x-silverlight', + 173 => 'application/x-stuffit', + 174 => 'application/x-sv4cpio', + 175 => 'application/x-sv4crc', + 176 => 'application/x-tar', + 177 => 'application/x-tcl', + 178 => 'application/x-tex-gf', + 179 => 'application/x-tex-pk', + 180 => 'application/x-texinfo', + 181 => 'application/x-troff', + 182 => 'application/x-troff-man', + 183 => 'application/x-troff-me', + 184 => 'application/x-troff-ms', + 185 => 'application/x-ustar', + 186 => 'application/x-wais-source', + 187 => 'application/x-wingz', + 188 => 'application/x-x509-ca-cert', + 189 => 'application/x-xcf', + 190 => 'application/x-xfig', + 191 => 'application/x-xpinstall', + 192 => 'audio/amr', + 193 => 'audio/amr-wb', + 194 => 'audio/amr', + 195 => 'audio/amr-wb', + 196 => 'audio/annodex', + 197 => 'audio/basic', + 198 => 'audio/flac', + 199 => 'audio/midi', + 200 => 'audio/mpeg', + 201 => 'audio/mpegurl', + 202 => 'audio/ogg', + 203 => 'audio/prs.sid', + 204 => 'audio/x-aiff', + 205 => 'audio/x-gsm', + 206 => 'audio/x-mpegurl', + 207 => 'audio/x-ms-wma', + 208 => 'audio/x-ms-wax', + 209 => 'audio/x-pn-realaudio', + 210 => 'audio/x-realaudio', + 211 => 'audio/x-scpls', + 212 => 'audio/x-sd2', + 213 => 'audio/x-wav', + 214 => 'chemical/x-alchemy', + 215 => 'chemical/x-cache', + 216 => 'chemical/x-cache-csf', + 217 => 'chemical/x-cactvs-binary', + 218 => 'chemical/x-cdx', + 219 => 'chemical/x-cerius', + 220 => 'chemical/x-chem3d', + 221 => 'chemical/x-chemdraw', + 222 => 'chemical/x-cif', + 223 => 'chemical/x-cmdf', + 224 => 'chemical/x-cml', + 225 => 'chemical/x-compass', + 226 => 'chemical/x-crossfire', + 227 => 'chemical/x-csml', + 228 => 'chemical/x-ctx', + 229 => 'chemical/x-cxf', + 230 => 'chemical/x-daylight-smiles', + 231 => 'chemical/x-embl-dl-nucleotide', + 232 => 'chemical/x-galactic-spc', + 233 => 'chemical/x-gamess-input', + 234 => 'chemical/x-gaussian-checkpoint', + 235 => 'chemical/x-gaussian-cube', + 236 => 'chemical/x-gaussian-input', + 237 => 'chemical/x-gaussian-log', + 238 => 'chemical/x-gcg8-sequence', + 239 => 'chemical/x-genbank', + 240 => 'chemical/x-hin', + 241 => 'chemical/x-isostar', + 242 => 'chemical/x-jcamp-dx', + 243 => 'chemical/x-kinemage', + 244 => 'chemical/x-macmolecule', + 245 => 'chemical/x-macromodel-input', + 246 => 'chemical/x-mdl-molfile', + 247 => 'chemical/x-mdl-rdfile', + 248 => 'chemical/x-mdl-rxnfile', + 249 => 'chemical/x-mdl-sdfile', + 250 => 'chemical/x-mdl-tgf', + 251 => 'chemical/x-mif', + 252 => 'chemical/x-mmcif', + 253 => 'chemical/x-mol2', + 254 => 'chemical/x-molconn-Z', + 255 => 'chemical/x-mopac-graph', + 256 => 'chemical/x-mopac-input', + 257 => 'chemical/x-mopac-out', + 258 => 'chemical/x-mopac-vib', + 259 => 'chemical/x-ncbi-asn1', + 260 => 'chemical/x-ncbi-asn1-ascii', + 261 => 'chemical/x-ncbi-asn1-binary', + 262 => 'chemical/x-ncbi-asn1-spec', + 263 => 'chemical/x-pdb', + 264 => 'chemical/x-rosdal', + 265 => 'chemical/x-swissprot', + 266 => 'chemical/x-vamas-iso14976', + 267 => 'chemical/x-vmd', + 268 => 'chemical/x-xtel', + 269 => 'chemical/x-xyz', + 270 => 'image/gif', + 271 => 'image/ief', + 272 => 'image/jpeg', + 273 => 'image/pcx', + 274 => 'image/png', + 275 => 'image/svg+xml', + 276 => 'image/tiff', + 277 => 'image/vnd.djvu', + 278 => 'image/vnd.wap.wbmp', + 279 => 'image/x-canon-cr2', + 280 => 'image/x-canon-crw', + 281 => 'image/x-cmu-raster', + 282 => 'image/x-coreldraw', + 283 => 'image/x-coreldrawpattern', + 284 => 'image/x-coreldrawtemplate', + 285 => 'image/x-corelphotopaint', + 286 => 'image/x-epson-erf', + 287 => 'image/x-icon', + 288 => 'image/x-jg', + 289 => 'image/x-jng', + 290 => 'image/x-ms-bmp', + 291 => 'image/x-nikon-nef', + 292 => 'image/x-olympus-orf', + 293 => 'image/x-photoshop', + 294 => 'image/x-portable-anymap', + 295 => 'image/x-portable-bitmap', + 296 => 'image/x-portable-graymap', + 297 => 'image/x-portable-pixmap', + 298 => 'image/x-rgb', + 299 => 'image/x-xbitmap', + 300 => 'image/x-xpixmap', + 301 => 'image/x-xwindowdump', + 302 => 'message/rfc822', + 303 => 'model/iges', + 304 => 'model/mesh', + 305 => 'model/vrml', + 306 => 'model/x3d+vrml', + 307 => 'model/x3d+xml', + 308 => 'model/x3d+binary', + 309 => 'text/cache-manifest', + 310 => 'text/calendar', + 311 => 'text/css', + 312 => 'text/csv', + 313 => 'text/h323', + 314 => 'text/html', + 315 => 'text/iuls', + 316 => 'text/mathml', + 317 => 'text/plain', + 318 => 'text/richtext', + 319 => 'text/scriptlet', + 320 => 'text/texmacs', + 321 => 'text/tab-separated-values', + 322 => 'text/vnd.sun.j2me.app-descriptor', + 323 => 'text/vnd.wap.wml', + 324 => 'text/vnd.wap.wmlscript', + 325 => 'text/x-bibtex', + 326 => 'text/x-boo', + 327 => 'text/x-c++hdr', + 328 => 'text/x-c++src', + 329 => 'text/x-chdr', + 330 => 'text/x-component', + 331 => 'text/x-csh', + 332 => 'text/x-csrc', + 333 => 'text/x-dsrc', + 334 => 'text/x-diff', + 335 => 'text/x-haskell', + 336 => 'text/x-java', + 337 => 'text/x-literate-haskell', + 338 => 'text/x-moc', + 339 => 'text/x-pascal', + 340 => 'text/x-pcs-gcd', + 341 => 'text/x-perl', + 342 => 'text/x-python', + 343 => 'text/x-scala', + 344 => 'text/x-setext', + 345 => 'text/x-sh', + 346 => 'text/x-tcl', + 347 => 'text/x-tex', + 348 => 'text/x-vcalendar', + 349 => 'text/x-vcard', + 350 => 'video/3gpp', + 351 => 'video/annodex', + 352 => 'video/dl', + 353 => 'video/dv', + 354 => 'video/fli', + 355 => 'video/gl', + 356 => 'video/mpeg', + 357 => 'video/mp4', + 358 => 'video/quicktime', + 359 => 'video/ogg', + 360 => 'video/vnd.mpegurl', + 361 => 'video/x-flv', + 362 => 'video/x-la-asf', + 363 => 'video/x-mng', + 364 => 'video/x-ms-asf', + 365 => 'video/x-ms-wm', + 366 => 'video/x-ms-wmv', + 367 => 'video/x-ms-wmx', + 368 => 'video/x-ms-wvx', + 369 => 'video/x-msvideo', + 370 => 'video/x-sgi-movie', + 371 => 'video/x-matroska', + 372 => 'x-conference/x-cooltalk', + 373 => 'x-epoc/x-sisx-app', + 374 => 'x-world/x-vrml', + 375 => 'image/jpeg', + ); + + /* + * Extension map + */ + protected static $extensions = array( + 1 => 'ez', + 2 => 'anx', + 3 => 'atom', + 4 => 'atomcat', + 5 => 'atomsrv', + 6 => 'lin', + 7 => 'cap', + 8 => 'cu', + 9 => 'davmount', + 10 => 'tsp', + 11 => 'es', + 12 => 'spl', + 13 => 'hta', + 14 => 'jar', + 15 => 'ser', + 16 => 'class', + 17 => 'js', + 18 => 'm3g', + 19 => 'hqx', + 20 => 'cpt', + 21 => 'nb', + 22 => 'mdb', + 23 => 'doc', + 24 => 'mxf', + 25 => 'bin', + 26 => 'oda', + 27 => 'ogx', + 28 => 'pdf', + 29 => 'key', + 30 => 'pgp', + 31 => 'prf', + 32 => 'ps', + 33 => 'rar', + 34 => 'rdf', + 35 => 'rss', + 36 => 'rtf', + 37 => 'smi', + 38 => 'xhtml', + 39 => 'xml', + 40 => 'xspf', + 41 => 'zip', + 42 => 'apk', + 43 => 'cdy', + 44 => 'kml', + 45 => 'kmz', + 46 => 'xul', + 47 => 'xls', + 48 => 'cat', + 49 => 'stl', + 50 => 'ppt', + 51 => 'odc', + 52 => 'odb', + 53 => 'odf', + 54 => 'odg', + 55 => 'otg', + 56 => 'odi', + 57 => 'odp', + 58 => 'otp', + 59 => 'ods', + 60 => 'ots', + 61 => 'odt', + 62 => 'odm', + 63 => 'ott', + 64 => 'oth', + 65 => 'xlsx', + 66 => 'xltx', + 67 => 'pptx', + 68 => 'ppsx', + 69 => 'potx', + 70 => 'docx', + 71 => 'dotx', + 72 => 'cod', + 73 => 'mmf', + 74 => 'sdc', + 75 => 'sds', + 76 => 'sda', + 77 => 'sdd', + 78 => 'sdf', + 79 => 'sdw', + 80 => 'sgl', + 81 => 'sxc', + 82 => 'stc', + 83 => 'sxd', + 84 => 'std', + 85 => 'sxi', + 86 => 'sti', + 87 => 'sxm', + 88 => 'sxw', + 89 => 'sxg', + 90 => 'stw', + 91 => 'sis', + 92 => 'vsd', + 93 => 'wbxml', + 94 => 'wmlc', + 95 => 'wmlsc', + 96 => 'wpd', + 97 => 'wp5', + 98 => 'wk', + 99 => '7z', + 100 => 'abw', + 101 => 'dmg', + 102 => 'bcpio', + 103 => 'torrent', + 104 => 'cab', + 105 => 'cbr', + 106 => 'cbz', + 107 => 'cdf', + 108 => 'vcd', + 109 => 'pgn', + 110 => 'cpio', + 111 => 'csh', + 112 => 'deb', + 113 => 'dcr', + 114 => 'dms', + 115 => 'wad', + 116 => 'dvi', + 117 => 'rhtml', + 118 => 'pfa', + 119 => 'mm', + 120 => 'spl', + 121 => 'gnumeric', + 122 => 'sgf', + 123 => 'gcf', + 124 => 'gtar', + 125 => 'hdf', + 126 => 'phtml', + 127 => 'phps', + 128 => 'php3', + 129 => 'php3p', + 130 => 'php4', + 131 => 'php5', + 132 => 'ica', + 133 => 'info', + 134 => 'ins', + 135 => 'iii', + 136 => 'iso', + 137 => 'jam', + 138 => 'jnlp', + 139 => 'jmz', + 140 => 'chrt', + 141 => 'kil', + 142 => 'skp', + 143 => 'kpr', + 144 => 'ksp', + 145 => 'kwd', + 146 => 'latex', + 147 => 'lha', + 148 => 'lyx', + 149 => 'lzh', + 150 => 'lzx', + 151 => 'frm', + 152 => 'mif', + 153 => 'wmd', + 154 => 'wmz', + 155 => 'com', + 156 => 'msi', + 157 => 'nc', + 158 => 'pac', + 159 => 'nwc', + 160 => 'o', + 161 => 'oza', + 162 => 'p7r', + 163 => 'crl', + 164 => 'pyc', + 165 => 'qgs', + 166 => 'qtl', + 167 => 'rpm', + 168 => 'rb', + 169 => 'sh', + 170 => 'shar', + 171 => 'swf', + 172 => 'scr', + 173 => 'sit', + 174 => 'sv4cpio', + 175 => 'sv4crc', + 176 => 'tar', + 177 => 'tcl', + 178 => 'gf', + 179 => 'pk', + 180 => 'texinfo', + 181 => 't', + 182 => 'man', + 183 => 'me', + 184 => 'ms', + 185 => 'ustar', + 186 => 'src', + 187 => 'wz', + 188 => 'crt', + 189 => 'xcf', + 190 => 'fig', + 191 => 'xpi', + 192 => 'amr', + 193 => 'awb', + 194 => 'amr', + 195 => 'awb', + 196 => 'axa', + 197 => 'au', + 198 => 'flac', + 199 => 'mid', + 200 => 'mpga', + 201 => 'm3u', + 202 => 'oga', + 203 => 'sid', + 204 => 'aif', + 205 => 'gsm', + 206 => 'm3u', + 207 => 'wma', + 208 => 'wax', + 209 => 'ra', + 210 => 'ra', + 211 => 'pls', + 212 => 'sd2', + 213 => 'wav', + 214 => 'alc', + 215 => 'cac', + 216 => 'csf', + 217 => 'cbin', + 218 => 'cdx', + 219 => 'cer', + 220 => 'c3d', + 221 => 'chm', + 222 => 'cif', + 223 => 'cmdf', + 224 => 'cml', + 225 => 'cpa', + 226 => 'bsd', + 227 => 'csml', + 228 => 'ctx', + 229 => 'cxf', + 230 => 'smi', + 231 => 'emb', + 232 => 'spc', + 233 => 'inp', + 234 => 'fch', + 235 => 'cub', + 236 => 'gau', + 237 => 'gal', + 238 => 'gcg', + 239 => 'gen', + 240 => 'hin', + 241 => 'istr', + 242 => 'jdx', + 243 => 'kin', + 244 => 'mcm', + 245 => 'mmd', + 246 => 'mol', + 247 => 'rd', + 248 => 'rxn', + 249 => 'sd', + 250 => 'tgf', + 251 => 'mif', + 252 => 'mcif', + 253 => 'mol2', + 254 => 'b', + 255 => 'gpt', + 256 => 'mop', + 257 => 'moo', + 258 => 'mvb', + 259 => 'asn', + 260 => 'prt', + 261 => 'val', + 262 => 'asn', + 263 => 'pdb', + 264 => 'ros', + 265 => 'sw', + 266 => 'vms', + 267 => 'vmd', + 268 => 'xtel', + 269 => 'xyz', + 270 => 'gif', + 271 => 'ief', + 272 => 'jpg', + 273 => 'pcx', + 274 => 'png', + 275 => 'svg', + 276 => 'tiff', + 277 => 'djvu', + 278 => 'wbmp', + 279 => 'cr2', + 280 => 'crw', + 281 => 'ras', + 282 => 'cdr', + 283 => 'pat', + 284 => 'cdt', + 285 => 'cpt', + 286 => 'erf', + 287 => 'ico', + 288 => 'art', + 289 => 'jng', + 290 => 'bmp', + 291 => 'nef', + 292 => 'orf', + 293 => 'psd', + 294 => 'pnm', + 295 => 'pbm', + 296 => 'pgm', + 297 => 'ppm', + 298 => 'rgb', + 299 => 'xbm', + 300 => 'xpm', + 301 => 'xwd', + 302 => 'eml', + 303 => 'igs', + 304 => 'msh', + 305 => 'wrl', + 306 => 'x3dv', + 307 => 'x3d', + 308 => 'x3db', + 309 => 'manifest', + 310 => 'ics', + 311 => 'css', + 312 => 'csv', + 313 => '323', + 314 => 'html', + 315 => 'uls', + 316 => 'mml', + 317 => 'asc', + 318 => 'rtx', + 319 => 'sct', + 320 => 'tm', + 321 => 'tsv', + 322 => 'jad', + 323 => 'wml', + 324 => 'wmls', + 325 => 'bib', + 326 => 'boo', + 327 => 'h', + 328 => 'c', + 329 => 'h', + 330 => 'htc', + 331 => 'csh', + 332 => 'c', + 333 => 'd', + 334 => 'diff', + 335 => 'hs', + 336 => 'java', + 337 => 'lhs', + 338 => 'moc', + 339 => 'p', + 340 => 'gcd', + 341 => 'pl', + 342 => 'py', + 343 => 'scala', + 344 => 'etx', + 345 => 'sh', + 346 => 'tcl', + 347 => 'tex', + 348 => 'vcs', + 349 => 'vcf', + 350 => '3gp', + 351 => 'axv', + 352 => 'dl', + 353 => 'dif', + 354 => 'fli', + 355 => 'gl', + 356 => 'mpeg', + 357 => 'mp4', + 358 => 'qt', + 359 => 'ogv', + 360 => 'mxu', + 361 => 'flv', + 362 => 'lsf', + 363 => 'mng', + 364 => 'asf', + 365 => 'wm', + 366 => 'wmv', + 367 => 'wmx', + 368 => 'wvx', + 369 => 'avi', + 370 => 'movie', + 371 => 'mpv', + 372 => 'ice', + 373 => 'sisx', + 374 => 'vrm', + 375 => 'jpeg', + ); + + /** + * @return MimeType + */ + public static function wrap($id) + { + return new self($id); + } + + + /** + * Return MimeType object by mime-type string + * @param string $value + * @throws MissingElementException + * @return MimeType + */ + public static function getByMimeType($value) + { + $list = static::getNameList(); + + $id = array_search(mb_strtolower($value), $list); + if ($id === false) + throw new MissingElementException('Can not find similar mime type "'.$value.'" !'); + + return new self($id); + } + + /** + * Return MimeType object by extension without [dot] prefix + * @param string $value + * @throws MissingElementException + * @return MimeType + */ + public static function getByExtension($value) + { + $list = static::getExtensionList(); + + $id = array_search(mb_strtolower($value), $list); + if ($id === false) + throw new MissingElementException('Can not find similar extension "'.$value.'" !'); + + return new self($id); + } + + + /** + * Extension list + * @return array + */ + public static function getExtensionList() + { + return static::$extensions; + } + + /** + * Return extension without [dot] prefix. + * @throws MissingElementException + * @return string + */ + public function getExtension() + { + if( + isset( static::$extensions[$this->id] ) + ) + return static::$extensions[$this->id]; + + throw new MissingElementException( + 'Can not find "'.$this->id.'" in extensions map!' + ); + } + + /** + * Return Mime type string + * @return string + */ + public function getMimeType() + { + return $this->getName(); + } + } +?> \ No newline at end of file diff --git a/main/Base/NamedTree.class.php b/main/Base/NamedTree.class.php old mode 100644 new mode 100755 diff --git a/main/Base/Range.class.php b/main/Base/Range.class.php old mode 100644 new mode 100755 diff --git a/main/Base/RequestType.class.php b/main/Base/RequestType.class.php old mode 100644 new mode 100755 diff --git a/main/Base/SerializedObjectComparator.class.php b/main/Base/SerializedObjectComparator.class.php old mode 100644 new mode 100755 diff --git a/main/Base/SingleRange.class.php b/main/Base/SingleRange.class.php old mode 100644 new mode 100755 diff --git a/main/Base/StandardComparator.class.php b/main/Base/StandardComparator.class.php old mode 100644 new mode 100755 diff --git a/main/Base/TempDirectory.class.php b/main/Base/TempDirectory.class.php old mode 100644 new mode 100755 diff --git a/main/Base/TempFile.class.php b/main/Base/TempFile.class.php old mode 100644 new mode 100755 diff --git a/main/Base/TimestampRange.class.php b/main/Base/TimestampRange.class.php old mode 100644 new mode 100755 diff --git a/main/Base/TranslatedStore.class.php b/main/Base/TranslatedStore.class.php new file mode 100644 index 0000000000..958bddc997 --- /dev/null +++ b/main/Base/TranslatedStore.class.php @@ -0,0 +1,32 @@ +toValue($string); + } +} \ No newline at end of file diff --git a/main/Base/TransparentFile.class.php b/main/Base/TransparentFile.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/BaseGoogleChartData.class.php b/main/Charts/Google/BaseGoogleChartData.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/BaseGoogleChartDataEncoding.class.php b/main/Charts/Google/BaseGoogleChartDataEncoding.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/BaseGoogleChartLabelStyleType.class.php b/main/Charts/Google/BaseGoogleChartLabelStyleType.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/BaseGoogleChartParameter.class.php b/main/Charts/Google/BaseGoogleChartParameter.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/BaseGoogleChartStyle.class.php b/main/Charts/Google/BaseGoogleChartStyle.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/ChartLabelStyle.class.php b/main/Charts/Google/ChartLabelStyle.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/ChartLineStyle.class.php b/main/Charts/Google/ChartLineStyle.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChart.class.php b/main/Charts/Google/GoogleChart.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartAxis.class.php b/main/Charts/Google/GoogleChartAxis.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartAxisCollection.class.php b/main/Charts/Google/GoogleChartAxisCollection.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartAxisLabel.class.php b/main/Charts/Google/GoogleChartAxisLabel.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartAxisType.class.php b/main/Charts/Google/GoogleChartAxisType.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartColor.class.php b/main/Charts/Google/GoogleChartColor.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartData.class.php b/main/Charts/Google/GoogleChartData.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartDataEncoding.class.php b/main/Charts/Google/GoogleChartDataEncoding.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartDataRange.class.php b/main/Charts/Google/GoogleChartDataRange.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartDataScaling.class.php b/main/Charts/Google/GoogleChartDataScaling.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartDataSet.class.php b/main/Charts/Google/GoogleChartDataSet.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartDataSimpleEncoding.class.php b/main/Charts/Google/GoogleChartDataSimpleEncoding.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartDataTextEncoding.class.php b/main/Charts/Google/GoogleChartDataTextEncoding.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartGrid.class.php b/main/Charts/Google/GoogleChartGrid.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartLabel.class.php b/main/Charts/Google/GoogleChartLabel.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartLabelStyle.class.php b/main/Charts/Google/GoogleChartLabelStyle.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartLabelStyleNumberType.class.php b/main/Charts/Google/GoogleChartLabelStyleNumberType.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartLegend.class.php b/main/Charts/Google/GoogleChartLegend.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartLegendPositionType.class.php b/main/Charts/Google/GoogleChartLegendPositionType.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartLine.class.php b/main/Charts/Google/GoogleChartLine.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartLineStyle.class.php b/main/Charts/Google/GoogleChartLineStyle.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartPiece.class.php b/main/Charts/Google/GoogleChartPiece.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartSize.class.php b/main/Charts/Google/GoogleChartSize.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartSolidFill.class.php b/main/Charts/Google/GoogleChartSolidFill.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartSolidFillCollection.class.php b/main/Charts/Google/GoogleChartSolidFillCollection.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartSolidFillType.class.php b/main/Charts/Google/GoogleChartSolidFillType.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartTitle.class.php b/main/Charts/Google/GoogleChartTitle.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleChartType.class.php b/main/Charts/Google/GoogleChartType.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleGridedLineChart.class.php b/main/Charts/Google/GoogleGridedLineChart.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleLineChart.class.php b/main/Charts/Google/GoogleLineChart.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GoogleNormalizedLineChart.class.php b/main/Charts/Google/GoogleNormalizedLineChart.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/GooglePieChart.class.php b/main/Charts/Google/GooglePieChart.class.php old mode 100644 new mode 100755 diff --git a/main/Charts/Google/LabelStyleType.class.php b/main/Charts/Google/LabelStyleType.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/Criteria.class.php b/main/Criteria/Criteria.class.php old mode 100644 new mode 100755 index 08e97142ab..3666e7340d --- a/main/Criteria/Criteria.class.php +++ b/main/Criteria/Criteria.class.php @@ -11,7 +11,7 @@ /** * @see http://www.hibernate.org/hib_docs/v3/reference/en/html/querycriteria.html - * + * * @ingroup Criteria **/ final class Criteria extends QueryIdentification @@ -22,17 +22,23 @@ final class Criteria extends QueryIdentification private $order = null; private $strategy = null; private $projection = null; - + private $distinct = false; - + private $limit = null; private $offset = null; - + private $collections = array(); - + // dao-like behaviour: will throw ObjectNotFoundException when 'false' private $silent = true; - + + // Кастомный запрос + private $selectQuery = null; + + // additional joins for cases + private $customJoins = array(); + /** * @return Criteria **/ @@ -40,19 +46,19 @@ public static function create(/* ProtoDAO */ $dao = null) { return new self($dao); } - + public function __construct(/* ProtoDAO */ $dao = null) { if ($dao) Assert::isTrue($dao instanceof ProtoDAO); - + $this->dao = $dao; $this->logic = Expression::andBlock(); $this->order = new OrderChain(); $this->strategy = FetchStrategy::join(); $this->projection = Projection::chain(); } - + public function __clone() { $this->logic = clone $this->logic; @@ -60,25 +66,25 @@ public function __clone() $this->strategy = clone $this->strategy; $this->projection = clone $this->projection; } - + public function __sleep() { $this->daoClass = $this->getDao() ? get_class($this->dao) : null; - + $vars = get_object_vars($this); unset($vars['dao']); return array_keys($vars); } - + public function __wakeup() { if ($this->daoClass) $this->dao = Singleton::getInstance($this->daoClass); } - + /** * @return ProtoDAO **/ @@ -86,25 +92,29 @@ public function getDao() { return $this->dao; } - + /** * @return Criteria **/ public function setDao(ProtoDAO $dao) { $this->dao = $dao; - + return $this; } - + + /** + * @return ProtoDAO + * @throws WrongStateException + */ public function checkAndGetDao() { if (!$this->dao) throw new WrongStateException('You forgot to set dao'); - + return $this->dao; } - + /** * @return LogicalChain **/ @@ -112,17 +122,37 @@ public function getLogic() { return $this->logic; } - + + /** + * @param LogicalChain $logic + * @return Criteria + */ + public function setLogic(LogicalChain $logic) { + $this->logic = $logic; + + return $this; + } + + /** + * @return Criteria + */ + public function dropLogic() + { + $this->logic = Expression::andBlock(); + + return $this; + } + /** * @return Criteria **/ public function add(LogicalObject $logic) { $this->logic->expAnd($logic); - + return $this; } - + /** * @return OrderChain **/ @@ -130,7 +160,17 @@ public function getOrder() { return $this->order; } - + + /** + * @param OrderChain $order + * @return Criteria + */ + public function setOrder(OrderChain $order) { + $this->order = $order; + + return $this; + } + /** * @return Criteria **/ @@ -138,12 +178,12 @@ public function addOrder(/* MapableObject */ $order) { if (!$order instanceof MappableObject) $order = new OrderBy($order); - + $this->order->add($order); - + return $this; } - + /** * @return Criteria **/ @@ -151,52 +191,52 @@ public function prependOrder(/* MapableObject */ $order) { if (!$order instanceof MappableObject) $order = new OrderBy($order); - + $this->order->prepend($order); - + return $this; } - + /** * @return Criteria **/ public function dropOrder() { $this->order = new OrderChain(); - + return $this; } - + public function getLimit() { return $this->limit; } - + /** * @return Criteria **/ public function setLimit($limit) { $this->limit = $limit; - + return $this; } - + public function getOffset() { return $this->offset; } - + /** * @return Criteria **/ public function setOffset($offset) { $this->offset = $offset; - + return $this; } - + /** * @return FetchStrategy **/ @@ -204,17 +244,17 @@ public function getFetchStrategy() { return $this->strategy; } - + /** * @return Criteria **/ public function setFetchStrategy(FetchStrategy $strategy) { $this->strategy = $strategy; - + return $this; } - + /** * @return Criteria **/ @@ -224,10 +264,10 @@ public function setProjection(ObjectProjection $chain) $this->projection = $chain; else $this->projection = Projection::chain()->add($chain); - + return $this; } - + /** * @return Criteria **/ @@ -238,10 +278,10 @@ public function addProjection(ObjectProjection $projection) || !$projection->isEmpty() ) $this->projection->add($projection); - + return $this; } - + /** * @return ProjectionChain **/ @@ -249,49 +289,49 @@ public function getProjection() { return $this->projection; } - + /** * @return Criteria **/ public function dropProjection() { $this->projection = Projection::chain(); - + return $this; } - + /** * @return Criteria **/ public function setDistinct($orly = true) { $this->distinct = ($orly === true); - + return $this; } - + public function isDistinct() { return $this->distinct; } - + public function isSilent() { return $this->silent; } - + /** * @return Criteria **/ public function setSilent($silent) { Assert::isBoolean($silent); - + $this->silent = $silent; - + return $this; } - + /** * @return Criteria **/ @@ -306,7 +346,7 @@ public function fetchCollection( ($criteria === null) || ($criteria instanceof Criteria) ); - + $this->collections[$path]['lazy'] = $lazy; $this->collections[$path]['criteria'] = $criteria; $this->collections[$path]['propertyPath'] @@ -314,68 +354,74 @@ public function fetchCollection( $this->checkAndGetDao()->getObjectName(), $path ); - + return $this; } - + public function get() { try { - $list = array( - $this->checkAndGetDao()-> - getByQuery($this->toSelectQuery()) - ); + $dao = $this->checkAndGetDao(); + $list = + $dao instanceof NoSqlDAO + ? $dao->getListByCriteria($this) + : $dao->getListByQuery($this->toSelectQuery()); } catch (ObjectNotFoundException $e) { if (!$this->isSilent()) throw $e; - + return null; } - + if (!$this->collections || !$list) return reset($list); - + $list = $this->checkAndGetDao()-> fetchCollections($this->collections, $list); - + return reset($list); } - + public function getList() { try { + $dao = $this->checkAndGetDao(); $list = - $this->checkAndGetDao()-> - getListByQuery($this->toSelectQuery()); - + $dao instanceof NoSqlDAO + ? $dao->getListByCriteria($this) + : $dao->getListByQuery($this->toSelectQuery()); } catch (ObjectNotFoundException $e) { if (!$this->isSilent()) throw $e; - + return array(); } - + if (!$this->collections || !$list) return $list; - + return $this->checkAndGetDao()-> fetchCollections($this->collections, $list); } - + /** - * @return QueryResult + * @return QueryResult|NoSqlResult **/ public function getResult() { - $result = - $this->checkAndGetDao()-> - getQueryResult($this->toSelectQuery()); - + $dao = $this->checkAndGetDao(); + if ($dao instanceof NoSqlDAO) { + /** @var $dao NoSqlDAO */ + $result = $dao->getNoSqlResult($this); + } else { + $result = $dao->getQueryResult($this->toSelectQuery()); + } + if (!$this->collections || !$result->getCount()) return $result; - + return $result->setList( $this->checkAndGetDao()->fetchCollections( $this->collections, @@ -383,61 +429,68 @@ public function getResult() ) ); } - + public function getCustom($index = null) { try { $result = $this->checkAndGetDao()->getCustom($this->toSelectQuery()); - + if ($index) { if (array_key_exists($index, $result)) return $result[$index]; - + throw new MissingElementException( 'No such key: "'.$index.'" in result set.' ); } - + return $result; } catch (ObjectNotFoundException $e) { if (!$this->isSilent()) throw $e; - + return null; } } - + public function getCustomList() { try { return $this->checkAndGetDao()-> getCustomList($this->toSelectQuery()); - + } catch (ObjectNotFoundException $e) { if (!$this->isSilent()) throw $e; - + return array(); } } - + + /** + * @return Cursor + */ + public function getCursor() { + return Cursor::create($this->getDao(), $this->toSelectQuery()); + } + public function getPropertyList() { try { return $this->checkAndGetDao()-> getCustomRowList($this->toSelectQuery()); - + } catch (ObjectNotFoundException $e) { if (!$this->isSilent()) throw $e; - + return array(); } } - + public function toString() { return $this->toDialectString( @@ -446,17 +499,21 @@ public function toString() : ImaginaryDialect::me() ); } - + public function toDialectString(Dialect $dialect) { return $this->toSelectQuery()->toDialectString($dialect); } - + /** * @return SelectQuery **/ public function toSelectQuery() { + if( !is_null($this->selectQuery) ) { + return $this->selectQuery; + } + if (!$this->projection->isEmpty()) { $query = $this->getProjection()->process( @@ -466,13 +523,15 @@ public function toSelectQuery() ); } else $query = $this->checkAndGetDao()->makeSelectHead(); - + + $query->setCriteria($this); + if ($this->distinct) $query->distinct(); - + return $this->fillSelectQuery($query); } - + /** * @return SelectQuery **/ @@ -480,23 +539,23 @@ public function fillSelectQuery(SelectQuery $query) { $query-> limit($this->limit, $this->offset); - + if ($this->distinct) $query->distinct(); - + if ($this->logic->getSize()) { $query-> andWhere( $this->logic->toMapped($this->checkAndGetDao(), $query) ); } - + if ($this->order) { $query->setOrderChain( $this->order->toMapped($this->checkAndGetDao(), $query) ); } - + if ( $this->projection->isEmpty() && ( @@ -510,22 +569,37 @@ public function fillSelectQuery(SelectQuery $query) true ); } - + + if ($this->customJoins) { + foreach ($this->customJoins as $join) { + $query->customJoin($join); + } + } + return $query; } - + /** * @return Criteria **/ public function dropProjectionByType(/* array */ $dropTypes) { Assert::isInstance($this->projection, 'ProjectionChain'); - + $this->projection->dropByType($dropTypes); - + + return $this; + } + + /** + * @param SelectQuery $query + * @return Criteria + **/ + public function setSelectQuery(SelectQuery $query) { + $this->selectQuery = $query; return $this; } - + private function joinProperties( SelectQuery $query, ProtoDAO $parentDao, @@ -535,7 +609,7 @@ private function joinProperties( ) { $proto = call_user_func(array($parentDao->getObjectName(), 'proto')); - + foreach ($proto->getPropertyList() as $property) { if ( ($property instanceof LightMetaProperty) @@ -558,6 +632,14 @@ private function joinProperties( is_subclass_of( $property->getClassName(), 'Enumeration' + ) || + is_subclass_of( + $property->getClassName(), + 'Enum' + ) || + is_subclass_of( + $property->getClassName(), + 'Registry' ) ) { // field already added by makeSelectHead @@ -566,7 +648,7 @@ private function joinProperties( $proto = call_user_func( array($property->getClassName(), 'proto') ); - + foreach ($proto->getPropertyList() as $innerProperty) $query->get( new DBField( @@ -574,25 +656,25 @@ private function joinProperties( $parentTable ) ); - + continue; } - + $propertyDao = call_user_func( array($property->getClassName(), 'dao') ); - + // add's custom dao's injection possibility if (!$propertyDao instanceof ProtoDAO) continue; - + $tableAlias = $propertyDao->getJoinName( $property->getColumnName(), $prefix ); - + $fields = $propertyDao->getFields(); - + if (!$query->hasJoinedTable($tableAlias)) { $logic = Expression::eq( @@ -600,19 +682,19 @@ private function joinProperties( $property->getColumnName(), $parentTable ), - + DBField::create( $propertyDao->getIdName(), $tableAlias ) ); - + if ($property->isRequired() && $parentRequired) $query->join($propertyDao->getTable(), $logic, $tableAlias); else $query->leftJoin($propertyDao->getTable(), $logic, $tableAlias); } - + foreach ($fields as $field) { $query->get( new DBField($field, $tableAlias), @@ -620,7 +702,7 @@ private function joinProperties( .$field ); } - + $this->joinProperties( $query, $propertyDao, @@ -631,7 +713,7 @@ private function joinProperties( } } } - + /** * @return AbstractProtoClass **/ @@ -642,5 +724,22 @@ private function getProto() array($this->checkAndGetDao()->getObjectName(), 'proto') ); } + + /** + * @param SQLBaseJoin $customJoin + * @return $this + */ + public function addCustomJoin(SQLBaseJoin $customJoin) { + $this->customJoins []= $customJoin; + return $this; + } + + /** + * @return $this + */ + public function dropCustomJoins() { + $this->customJoins = array(); + return $this; + } } ?> \ No newline at end of file diff --git a/main/Criteria/Cursor.class.php b/main/Criteria/Cursor.class.php new file mode 100644 index 0000000000..7727384742 --- /dev/null +++ b/main/Criteria/Cursor.class.php @@ -0,0 +1,203 @@ + + * @date 2013.04.25 + */ +final class Cursor implements Iterator { + + /** @var ProtoDAO */ + protected $dao = null; + + /** @var SelectQuery */ + protected $selectQuery = null; + + /** @var DB */ + protected $db = null; + + /** @var string */ + protected $cursorName = null; + + /** @var array */ + protected $buffer = array(); + + /** @var int */ + protected $batchSize = 250; + + /** @var bool */ + protected $iterateObjects = null; + + /** @var int */ + protected $iteratorPosition = 0; + + /** @var mixed */ + protected $iteratorCurrent = null; + + public static function create(ProtoDAO $dao, SelectQuery $query = null) { + return new self($dao, $query); + } + + public function __construct(ProtoDAO $dao, SelectQuery $query = null) { + if ($query) + Assert::isTrue($query instanceof SelectQuery); + + $this->dao = $dao; + $this->db = DBPool::getByDao($this->dao); + $this->selectQuery = $query; + + $this->openTransaction(); + $this->declareCursor(); + } + + function __destruct() { + if( $this->db->inTransaction() && is_resource($this->db->getLink()) ) { + $this->closeCursor(); + $this->closeTransaction(); + } + } + + public function __clone() + { + $this->dao = clone $this->dao; + $this->selectQuery = clone $this->selectQuery; + } + + /** + * @return Cursor + */ + public function asObjects() { + $this->iterateObjects = true; + return $this; + } + + /** + * @return Cursor + */ + public function asRows() { + $this->iterateObjects = false; + return $this; + } + + public function setBatchSize($size) { + if( Assert::checkInteger($size) && $size>0 ) { + $this->batchSize = $size; + } + return $this; + } + + /** + * @param bool $strict + * @throws ObjectNotFoundException + * @return Prototyped|bool + */ + public function getNext($strict=false) { + $row = $this->fetchRow(); + if( !$row ) { + if( $strict ) { + throw new ObjectNotFoundException(); + } + return false; + } + return $this->dao->getProtoClass()->makeOnlyObject($this->dao->getObjectName(), $row); + } + + /** + * @param bool $strict + * @throws ObjectNotFoundException + * @return array|bool + */ + public function getNextRow($strict=false) { + $row = $this->fetchRow(); + if( !$row ) { + if( $strict ) { + throw new ObjectNotFoundException(); + } + return false; + } + return $row; + } + + public function current() { + return $this->iteratorCurrent; + } + + public function key() { + return $this->iteratorPosition; + } + + public function next() { + $this->iteratorPosition++; + } + + public function valid() { + $this->iteratorCurrent = $this->iterateObjects ? $this->getNext() : $this->getNextRow(); + return $this->iteratorCurrent===false ? false : true; + } + + public function rewind() { + if( is_null($this->iterateObjects) ) { + throw new WrongStateException('Type of iterating objects is not defined, use asObjects() or asRows()'); + } + $this->iteratorPosition = 0; + } + + public function close() { + $this->closeCursor(); + $this->closeTransaction(); + $this->cursorName = null; + } + + + /** + * @return SelectQuery + */ + protected function getSelectQuery() { + if( is_null($this->selectQuery) ) { + $this->selectQuery = $this->dao->makeSelectHead(); + } + return $this->selectQuery; + + } + + /** + * @return string + */ + protected function getCursorName() { + if( is_null($this->cursorName) ) { + $this->cursorName = 'cursor_'.dechex(crc32(time().$this->dao->getTable())); + } + return $this->cursorName; + } + + protected function openTransaction() { + $this->db->begin(); + } + + protected function declareCursor() { + $queryDeclare = 'DECLARE '.$this->getCursorName().' CURSOR FOR '.$this->getSelectQuery()->toDialectString($this->db->getDialect()); + $this->db->queryRaw($queryDeclare); + } + + protected function fetchRow() { + if( empty($this->buffer) ) { + $resource = $this->db->queryRaw('FETCH FORWARD 250 FROM '.$this->getCursorName()); + $this->buffer = pg_fetch_all($resource); + } + if( !$this->buffer ) { + return false; + } + return array_shift($this->buffer); + } + + protected function closeCursor() { + $queryOpen = 'CLOSE '.$this->getCursorName(); + $this->db->queryRaw($queryOpen); + } + + protected function closeTransaction() { + $this->db->commit(); + } + + final private function __sleep() {/* restless class */} + final private function __wakeup() {/* restless class */} +} \ No newline at end of file diff --git a/main/Criteria/FetchStrategy.class.php b/main/Criteria/FetchStrategy.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/Projection.class.php b/main/Criteria/Projection.class.php old mode 100644 new mode 100755 index eeff26453f..92f12eaf87 --- a/main/Criteria/Projection.class.php +++ b/main/Criteria/Projection.class.php @@ -119,5 +119,13 @@ public static function clazz($className) { return new ClassProjection($className); } + + /** + * @return CrippleClassProjection + **/ + public static function crippleClass($className) + { + return new CrippleClassProjection($className); + } } ?> \ No newline at end of file diff --git a/main/Criteria/Projections/AggregateProjection.class.php b/main/Criteria/Projections/AggregateProjection.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/Projections/AverageNumberProjection.class.php b/main/Criteria/Projections/AverageNumberProjection.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/Projections/BaseProjection.class.php b/main/Criteria/Projections/BaseProjection.class.php old mode 100644 new mode 100755 index dd3025f3a9..b7f9ff96eb --- a/main/Criteria/Projections/BaseProjection.class.php +++ b/main/Criteria/Projections/BaseProjection.class.php @@ -22,10 +22,20 @@ public function __construct($propertyName = null, $alias = null) $this->property = $propertyName; $this->alias = $alias; } - + public function getAlias() { return $this->alias; } + + public function getPropertyName() + { + return $this->property; + } + + public function setPropertyName($propertyName = null) + { + $this->property = $propertyName; + } } ?> \ No newline at end of file diff --git a/main/Criteria/Projections/ClassProjection.class.php b/main/Criteria/Projections/ClassProjection.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/Projections/CountProjection.class.php b/main/Criteria/Projections/CountProjection.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/Projections/CrippleClassProjection.class.php b/main/Criteria/Projections/CrippleClassProjection.class.php new file mode 100644 index 0000000000..a98cbda369 --- /dev/null +++ b/main/Criteria/Projections/CrippleClassProjection.class.php @@ -0,0 +1,29 @@ + + * @author Alex Gorbylev + * @date 2012.12.06 + */ +class CrippleClassProjection extends ClassProjection { + + private $excludedFields = array(); + + public function excludeField($field) { + Assert::isString($field); + $this->excludedFields[$field] = $field; + return $this; + } + + /* void */ + protected function subProcess(JoinCapableQuery $query, DBField $field) { + // if need to exclude change field to NULL + if( array_key_exists($field->getField(), $this->excludedFields) ) { + $nullField = DBRaw::create('NULL'); + $query->get($nullField, $field->getField()); + } else { + $query->get($field); + } + } + + +} diff --git a/main/Criteria/Projections/DistinctCountProjection.class.php b/main/Criteria/Projections/DistinctCountProjection.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/Projections/GroupByClassProjection.class.php b/main/Criteria/Projections/GroupByClassProjection.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/Projections/GroupByPropertyProjection.class.php b/main/Criteria/Projections/GroupByPropertyProjection.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/Projections/HavingProjection.class.php b/main/Criteria/Projections/HavingProjection.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/Projections/MappableObjectProjection.class.php b/main/Criteria/Projections/MappableObjectProjection.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/Projections/MaximalNumberProjection.class.php b/main/Criteria/Projections/MaximalNumberProjection.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/Projections/MinimalNumberProjection.class.php b/main/Criteria/Projections/MinimalNumberProjection.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/Projections/ObjectProjection.class.php b/main/Criteria/Projections/ObjectProjection.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/Projections/ProjectionChain.class.php b/main/Criteria/Projections/ProjectionChain.class.php old mode 100644 new mode 100755 index b9c0c8e4c3..1d225a1ac9 --- a/main/Criteria/Projections/ProjectionChain.class.php +++ b/main/Criteria/Projections/ProjectionChain.class.php @@ -31,6 +31,19 @@ public function add(ObjectProjection $projection, $name = null) return $this; } + + /** + * @return array + */ + public function getList() + { + return $this->list; + } + + public function dropChain() + { + $this->list = array(); + } /** * @return JoinCapableQuery @@ -39,7 +52,7 @@ public function process(Criteria $criteria, JoinCapableQuery $query) { foreach ($this->list as $projection) $projection->process($criteria, $query); - + return $query; } diff --git a/main/Criteria/Projections/PropertyProjection.class.php b/main/Criteria/Projections/PropertyProjection.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/Projections/RowCountProjection.class.php b/main/Criteria/Projections/RowCountProjection.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/Projections/SumProjection.class.php b/main/Criteria/Projections/SumProjection.class.php old mode 100644 new mode 100755 diff --git a/main/Criteria/PropertyPath.class.php b/main/Criteria/PropertyPath.class.php old mode 100644 new mode 100755 diff --git a/main/Crypto/Base62Utils.class.php b/main/Crypto/Base62Utils.class.php old mode 100644 new mode 100755 diff --git a/main/Crypto/Crypter.class.php b/main/Crypto/Crypter.class.php old mode 100644 new mode 100755 diff --git a/main/Crypto/CryptoFunctions.class.php b/main/Crypto/CryptoFunctions.class.php old mode 100644 new mode 100755 diff --git a/main/Crypto/DiffieHellmanKeyPair.class.php b/main/Crypto/DiffieHellmanKeyPair.class.php old mode 100644 new mode 100755 diff --git a/main/Crypto/DiffieHellmanParameters.class.php b/main/Crypto/DiffieHellmanParameters.class.php old mode 100644 new mode 100755 diff --git a/main/Crypto/KeyPair.class.php b/main/Crypto/KeyPair.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/BaseDAO.class.php b/main/DAOs/BaseDAO.class.php old mode 100644 new mode 100755 index 8ddbeb4cef..c976a8f57f --- a/main/DAOs/BaseDAO.class.php +++ b/main/DAOs/BaseDAO.class.php @@ -53,6 +53,8 @@ public function dropByIds(array $ids); public function uncacheById($id); public function uncacheByIds($ids); public function uncacheLists(); + public function uncacheByQuery(SelectQuery $query); + public function uncacheItems(); //@} } ?> \ No newline at end of file diff --git a/main/DAOs/DAOConnected.class.php b/main/DAOs/DAOConnected.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/EmptyDAO.class.php b/main/DAOs/EmptyDAO.class.php new file mode 100644 index 0000000000..7a420942aa --- /dev/null +++ b/main/DAOs/EmptyDAO.class.php @@ -0,0 +1,18 @@ + + * @date 2013.04.19 + */ + +abstract class EmptyDAO extends Singleton { + + public function getIdName() { + return 'id'; + } + + public function getById($id) { + throw new UnimplementedFeatureException; + } + +} \ No newline at end of file diff --git a/main/DAOs/FullTextDAO.class.php b/main/DAOs/FullTextDAO.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/GenericDAO.class.php b/main/DAOs/GenericDAO.class.php old mode 100644 new mode 100755 index ab0cd44dd5..ee5c0d5b92 --- a/main/DAOs/GenericDAO.class.php +++ b/main/DAOs/GenericDAO.class.php @@ -11,16 +11,18 @@ /** * Basis of all DAO's. - * + * * @ingroup DAOs **/ abstract class GenericDAO extends Singleton implements BaseDAO { private $identityMap = array(); - + + private $triggersAllowed = true; + abstract public function getTable(); abstract public function getObjectName(); - + public function makeObject($array, $prefix = null) { if ( @@ -33,16 +35,16 @@ public function makeObject($array, $prefix = null) $this->getProtoClass()->skipObjectPrefetching( $this->identityMap[$array[$idName]] ); - + return $this->identityMap[$array[$idName]]; } - + return $this->completeObject( $this->makeOnlyObject($array, $prefix) ); } - + public function makeOnlyObject($array, $prefix = null) { // adding incomplete object to identity map @@ -53,7 +55,7 @@ public function makeOnlyObject($array, $prefix = null) ) ); } - + public function completeObject(Identifiable $object) { return $this->getProtoClass()->completeObject( @@ -62,82 +64,82 @@ public function completeObject(Identifiable $object) $this->addObjectToMap($object) ); } - + /** * Returns link name which is used to get actual DB-link from DBPool, * returning null by default for single-source projects. - * + * * @see DBPool **/ public function getLinkName() { return null; } - + public function getIdName() { return 'id'; } - + public function getSequence() { return $this->getTable().'_id'; } - + /** * @return AbstractProtoClass **/ public function getProtoClass() { static $protos = array(); - + if (!isset($protos[$className = $this->getObjectName()])) $protos[$className] = call_user_func(array($className, 'proto')); - + return $protos[$className]; } - + public function getMapping() { return $this->getProtoClass()->getMapping(); } - + public function getFields() { static $fields = array(); - + $className = $this->getObjectName(); - + if (!isset($fields[$className])) { $fields[$className] = array_values($this->getMapping()); } - + return $fields[$className]; } - + /** * @return SelectQuery **/ public function makeSelectHead() { static $selectHead = array(); - + if (!isset($selectHead[$className = $this->getObjectName()])) { $table = $this->getTable(); - + $object = OSQL::select()-> from($table); - + foreach ($this->getFields() as $field) $object->get(new DBField($field, $table)); - + $selectHead[$className] = $object; } - + return clone $selectHead[$className]; } - + /** * @return SelectQuery **/ @@ -150,22 +152,22 @@ public function makeTotalCountQuery() )-> from($this->getTable()); } - + /// boring delegates //@{ public function getById($id, $expires = Cache::EXPIRES_MEDIUM) { Assert::isScalar($id); Assert::isNotEmpty($id); - + if (isset($this->identityMap[$id])) return $this->identityMap[$id]; - + return $this->addObjectToMap( Cache::worker($this)->getById($id, $expires) ); } - + public function getByLogic( LogicalObject $logic, $expires = Cache::DO_NOT_CACHE ) @@ -174,7 +176,7 @@ public function getByLogic( Cache::worker($this)->getByLogic($logic, $expires) ); } - + public function getByQuery( SelectQuery $query, $expires = Cache::DO_NOT_CACHE ) @@ -183,20 +185,20 @@ public function getByQuery( Cache::worker($this)->getByQuery($query, $expires) ); } - + public function getCustom( SelectQuery $query, $expires = Cache::DO_NOT_CACHE ) { return Cache::worker($this)->getCustom($query, $expires); } - + public function getListByIds( array $ids, $expires = Cache::EXPIRES_MEDIUM ) { $mapped = $remain = array(); - + foreach ($ids as $id) { if (isset($this->identityMap[$id])) { $mapped[] = $this->identityMap[$id]; @@ -204,18 +206,18 @@ public function getListByIds( $remain[] = $id; } } - + if ($remain) { $list = $this->addObjectListToMap( Cache::worker($this)->getListByIds($remain, $expires) ); - + $mapped = array_merge($mapped, $list); } - + return ArrayUtils::regularizeList($ids, $mapped); } - + public function getListByQuery( SelectQuery $query, $expires = Cache::DO_NOT_CACHE ) @@ -224,7 +226,7 @@ public function getListByQuery( Cache::worker($this)->getListByQuery($query, $expires) ); } - + public function getListByLogic( LogicalObject $logic, $expires = Cache::DO_NOT_CACHE ) @@ -233,102 +235,129 @@ public function getListByLogic( Cache::worker($this)->getListByLogic($logic, $expires) ); } - + public function getPlainList($expires = Cache::EXPIRES_MEDIUM) { return $this->addObjectListToMap( Cache::worker($this)->getPlainList($expires) ); } - + public function getTotalCount($expires = Cache::DO_NOT_CACHE) { return Cache::worker($this)->getTotalCount($expires); } - + public function getCustomList( SelectQuery $query, $expires = Cache::DO_NOT_CACHE ) { return Cache::worker($this)->getCustomList($query, $expires); } - + public function getCustomRowList( SelectQuery $query, $expires = Cache::DO_NOT_CACHE ) { return Cache::worker($this)->getCustomRowList($query, $expires); } - + public function getQueryResult( SelectQuery $query, $expires = Cache::DO_NOT_CACHE ) { return Cache::worker($this)->getQueryResult($query, $expires); } - + public function drop(Identifiable $object) { $this->checkObjectType($object); - + return $this->dropById($object->getId()); } - + public function dropById($id) { + call_user_func($this->prepareTrigger($id, 'onBeforeDrop')); + $after = $this->prepareTrigger($id, 'onAfterDrop'); + unset($this->identityMap[$id]); - + $count = Cache::worker($this)->dropById($id); - + + call_user_func($after); + if (1 != $count) throw new WrongStateException('no object were dropped'); - + return $count; } - + public function dropByIds(array $ids) { + call_user_func($this->prepareTrigger($ids, 'onBeforeDrop')); + $after = $this->prepareTrigger($ids, 'onAfterDrop'); + foreach ($ids as $id) unset($this->identityMap[$id]); - + $count = Cache::worker($this)->dropByIds($ids); - + + call_user_func($after); + if ($count != count($ids)) throw new WrongStateException('not all objects were dropped'); - + return $count; } - + public function uncacheById($id) { unset($this->identityMap[$id]); - + return Cache::worker($this)->uncacheById($id); } - + public function uncacheByIds($ids) { foreach ($ids as $id) unset($this->identityMap[$id]); - + return Cache::worker($this)->uncacheByIds($ids); } - + public function uncacheLists() { $this->dropIdentityMap(); - + return Cache::worker($this)->uncacheLists(); } - //@} - + + public function uncacheByQuery(SelectQuery $query) { + try { + $item = Cache::worker($this)->getByQuery($query); + if ($item instanceof Identifiable && isset($this->identityMap[$item->getId()])) { + unset($this->identityMap[$item->getId()]); + } + } catch (ObjectNotFoundException $e) {} + return Cache::worker($this)->uncacheByQuery($query); + } + + public function uncacheItems() { + + $this->dropIdentityMap(); + + return Cache::worker($this)->uncacheItems(); + } + //@} + /** * @return GenericDAO **/ public function dropIdentityMap() { $this->identityMap = array(); - + return $this; } @@ -338,34 +367,46 @@ public function dropObjectIdentityMapById($id) return $this; } - + + public function disableTriggers() { + $this->triggersAllowed = false; + return $this; + } + + public function enableTriggers() { + $this->triggersAllowed = true; + return $this; + } + protected function inject( InsertOrUpdateQuery $query, Identifiable $object ) { $this->checkObjectType($object); - - return $this->doInject( + + $this->runTrigger($object, 'onBeforeSave'); + + return $this->doInject( $this->setQueryFields( $query->setTable($this->getTable()), $object ), $object ); } - + protected function doInject( InsertOrUpdateQuery $query, Identifiable $object ) { $db = DBPool::getByDao($this); - + if (!$db->isQueueActive()) { $count = $db->queryCount($query); - + $this->uncacheById($object->getId()); - + if ($count !== 1) throw new WrongStateException( $count.' rows affected: racy or insane inject happened: ' @@ -373,14 +414,18 @@ protected function doInject( ); } else { $db->queryNull($query); - + $this->uncacheById($object->getId()); } - + // clean out Identifier, if any - return $this->addObjectToMap($object->setId($object->getId())); + $result = $this->addObjectToMap($object->setId($object->getId())); + + $this->runTrigger($object, 'onAfterSave'); + + return $result; } - + /* void */ protected function checkObjectType(Identifiable $object) { Assert::isSame( @@ -389,18 +434,52 @@ protected function doInject( 'strange object given, i can not inject it' ); } - + private function addObjectToMap(Identifiable $object) { return $this->identityMap[$object->getId()] = $object; } - + private function addObjectListToMap($list) { foreach ($list as $object) $this->identityMap[$object->getId()] = $object; - + return $list; } + + protected final function runTrigger($input, $triggerName) { + call_user_func($this->prepareTrigger($input, $triggerName)); + + return $this; + } + + protected final function prepareTrigger($input, $triggerName) { + if( + !$this->triggersAllowed || + !in_array($triggerName, class_implements($objName = $this->getObjectName())) + ) { + return (function(){ }); + } + + $check = function($obj) use ($objName) { + if(!($obj instanceof $objName)) { + $obj = $this->getById($obj); + } + return $obj; + }; + + if(is_array($input)) { + $input = array_map($check, $input); + } else { + $input = array($check($input)); + } + + return function() use (&$input, $triggerName) { + foreach($input as $obj) { + call_user_func(array($obj, $triggerName)); + } + }; + } } ?> \ No newline at end of file diff --git a/main/DAOs/Handlers/ApcSegmentHandler.class.php b/main/DAOs/Handlers/ApcSegmentHandler.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/Handlers/CacheSegmentHandler.class.php b/main/DAOs/Handlers/CacheSegmentHandler.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/Handlers/FileSystemSegmentHandler.class.php b/main/DAOs/Handlers/FileSystemSegmentHandler.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/Handlers/MessageSegmentHandler.class.php b/main/DAOs/Handlers/MessageSegmentHandler.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/Handlers/OptimizerSegmentHandler.class.php b/main/DAOs/Handlers/OptimizerSegmentHandler.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/Handlers/SegmentHandler.class.php b/main/DAOs/Handlers/SegmentHandler.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/Handlers/SharedMemorySegmentHandler.class.php b/main/DAOs/Handlers/SharedMemorySegmentHandler.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/Handlers/XCacheSegmentHandler.class.php b/main/DAOs/Handlers/XCacheSegmentHandler.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/Handlers/eAcceleratorSegmentHandler.class.php b/main/DAOs/Handlers/eAcceleratorSegmentHandler.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/Interfaces/onAfterDrop.class.php b/main/DAOs/Interfaces/onAfterDrop.class.php new file mode 100644 index 0000000000..e11c2029d8 --- /dev/null +++ b/main/DAOs/Interfaces/onAfterDrop.class.php @@ -0,0 +1,10 @@ + + * @date 2014.05.19 + */ + +interface onAfterDrop { + public function onAfterDrop(); +} \ No newline at end of file diff --git a/main/DAOs/Interfaces/onAfterSave.class.php b/main/DAOs/Interfaces/onAfterSave.class.php new file mode 100644 index 0000000000..e4cc8a4b19 --- /dev/null +++ b/main/DAOs/Interfaces/onAfterSave.class.php @@ -0,0 +1,10 @@ + + * @date 2014.05.19 + */ + +interface onAfterSave { + public function onAfterSave(); +} \ No newline at end of file diff --git a/main/DAOs/Interfaces/onBeforeDrop.class.php b/main/DAOs/Interfaces/onBeforeDrop.class.php new file mode 100644 index 0000000000..d8b9c8b94d --- /dev/null +++ b/main/DAOs/Interfaces/onBeforeDrop.class.php @@ -0,0 +1,10 @@ + + * @date 2014.05.19 + */ + +interface onBeforeDrop { + public function onBeforeDrop(); +} \ No newline at end of file diff --git a/main/DAOs/Interfaces/onBeforeSave.class.php b/main/DAOs/Interfaces/onBeforeSave.class.php new file mode 100644 index 0000000000..394e87c397 --- /dev/null +++ b/main/DAOs/Interfaces/onBeforeSave.class.php @@ -0,0 +1,10 @@ + + * @date 2014.05.19 + */ + +interface onBeforeSave { + public function onBeforeSave(); +} \ No newline at end of file diff --git a/main/DAOs/ProtoDAO.class.php b/main/DAOs/ProtoDAO.class.php old mode 100644 new mode 100755 index f2f12d7c38..07f2893374 --- a/main/DAOs/ProtoDAO.class.php +++ b/main/DAOs/ProtoDAO.class.php @@ -12,7 +12,7 @@ /** * @ingroup DAOs **/ - abstract class ProtoDAO extends GenericDAO + abstract class ProtoDAO extends TranslatableDAO { public function getJoinPrefix($field, $prefix = null) { @@ -165,7 +165,7 @@ protected function setQueryFields(InsertOrUpdateQuery $query, $object) return $this->getProtoClass()->fillQuery($query, $object); } - private function processPath( + final protected function processPath( AbstractProtoClass $proto, $probablyPath, JoinCapableQuery $query, @@ -376,7 +376,15 @@ public function guessAtom( $mapping = $this->getMapping() ) ) { - return new DBField($mapping[$atom], $table); + if ($this->isTranslatedField($atom)) { + return DBHstoreField::create( + $mapping[$atom], + $table, + $this->getLanguageCode() + ); + } else { + return new DBField($mapping[$atom], $table); + } } elseif ( ($query instanceof SelectQuery) && $query->hasAliasInside($atom) diff --git a/main/DAOs/StorableDAO.class.php b/main/DAOs/StorableDAO.class.php old mode 100644 new mode 100755 index 4787790be7..cbd031db10 --- a/main/DAOs/StorableDAO.class.php +++ b/main/DAOs/StorableDAO.class.php @@ -24,10 +24,16 @@ public function take(Identifiable $object) public function add(Identifiable $object) { + //Support non-"id" identifier columns + if (method_exists($this, 'getIdName')) { + $method = 'set'.ucfirst($this->getIdName()); + } else { + $method = 'setId'; + } return $this->inject( OSQL::insert(), - $object->setId( + $object->{$method}( DBPool::getByDao($this)->obtainSequence( $this->getSequence() ) @@ -81,8 +87,10 @@ public function unite( $object->getId(), $old->getId(), 'cannot merge different objects' ); - - $query = OSQL::update($this->getTable()); + + $this->runTrigger($object, 'onBeforeSave'); + + $query = OSQL::update($this->getTable()); foreach ($this->getProtoClass()->getPropertyList() as $property) { $getter = $property->getGetter(); diff --git a/main/DAOs/TranslatableDAO.class.php b/main/DAOs/TranslatableDAO.class.php new file mode 100644 index 0000000000..8e27f5a099 --- /dev/null +++ b/main/DAOs/TranslatableDAO.class.php @@ -0,0 +1,29 @@ +getProtoClass(); + return $proto->isPropertyExists($name) + && $proto->getPropertyByName($name)->isTranslated(); + } + + public function getLanguageCode() { + return call_user_func(array($this->getObjectName(), 'getLanguageCode')); + } +} \ No newline at end of file diff --git a/main/DAOs/Workers/BaseDaoWorker.class.php b/main/DAOs/Workers/BaseDaoWorker.class.php old mode 100644 new mode 100755 index fd67f3d22f..1a2e88fdbb --- a/main/DAOs/Workers/BaseDaoWorker.class.php +++ b/main/DAOs/Workers/BaseDaoWorker.class.php @@ -93,8 +93,14 @@ public function uncacheByQuery(SelectQuery $query) { return Cache::me()->mark($this->className)-> - delete($this->makeQueryKey($query, self::SUFFIX_QUERY)); + delete($this->makeQueryKey($query, $this->getSuffixQuery())); } + + public function uncacheItems() {} + + protected function getSuffixQuery() { + return self::SUFFIX_QUERY; + } //@} /// cache getters @@ -110,7 +116,7 @@ protected function getCachedByQuery(SelectQuery $query) { return Cache::me()->mark($this->className)-> - get($this->makeQueryKey($query, self::SUFFIX_QUERY)); + get($this->makeQueryKey($query, $this->getSuffixQuery())); } //@} diff --git a/main/DAOs/Workers/CacheDaoWorker.class.php b/main/DAOs/Workers/CacheDaoWorker.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/Workers/CommonDaoWorker.class.php b/main/DAOs/Workers/CommonDaoWorker.class.php old mode 100644 new mode 100755 index 0da3497dbf..6f72db4404 --- a/main/DAOs/Workers/CommonDaoWorker.class.php +++ b/main/DAOs/Workers/CommonDaoWorker.class.php @@ -199,43 +199,37 @@ public function getListByIds( $toFetch += array_keys($prefixed); if ($toFetch) { - try { - $list = - array_merge( - $list, - $this->getListByLogic( - Expression::in( - new DBField( - $this->dao->getIdName(), - $this->dao->getTable() - ), - $toFetch - ), - Cache::DO_NOT_CACHE - ) - ); - } catch (ObjectNotFoundException $e) { - // nothing to fetch - } - } - } elseif (count($ids)) { - try { $list = - $this->getListByLogic( - Expression::in( - new DBField( - $this->dao->getIdName(), - $this->dao->getTable() - ), - $ids - ), - Cache::DO_NOT_CACHE + array_merge( + $list, + $this->fetchListByIds($toFetch, $expires) ); - } catch (ObjectNotFoundException $e) {/*_*/} + } + + } elseif (count($ids)) { + $list = $this->fetchListByIds($ids, $expires); } return $list; } + + protected function fetchListByIds(array $ids, $expires = Cache::DO_NOT_CACHE) { + try { + return $this->getListByLogic( + Expression::in( + new DBField( + $this->dao->getIdName(), + $this->dao->getTable() + ), + $ids + ), + $expires + ); + + } catch (ObjectNotFoundException $e) { + return array(); + } + } public function getListByQuery( SelectQuery $query, $expires = Cache::DO_NOT_CACHE @@ -447,7 +441,7 @@ protected function cacheByQuery( Cache::me()->mark($this->className)-> add( - $this->makeQueryKey($query, self::SUFFIX_QUERY), + $this->makeQueryKey($query, $this->getSuffixQuery()), $object, $expires ); @@ -471,8 +465,9 @@ public function dropById($id) { $result = parent::dropById($id); - $this->dao->uncacheLists(); - +// $this->dao->uncacheLists(); + $this->uncacheLists(); + return $result; } //@} @@ -481,7 +476,8 @@ public function dropById($id) //@{ public function uncacheById($id) { - $this->dao->uncacheLists(); +// $this->dao->uncacheLists(); + $this->uncacheLists(); return parent::uncacheById($id); } diff --git a/main/DAOs/Workers/CustomDataScopedWorker.class.php b/main/DAOs/Workers/CustomDataScopedWorker.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/Workers/DalayedDropDaoWorker.class.php b/main/DAOs/Workers/DalayedDropDaoWorker.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/Workers/LinkedDaoWorker.class.php b/main/DAOs/Workers/LinkedDaoWorker.class.php new file mode 100644 index 0000000000..572a55d8ec --- /dev/null +++ b/main/DAOs/Workers/LinkedDaoWorker.class.php @@ -0,0 +1,228 @@ +getByQuery($this->makeIdKey($id, false), $expires); + } + + protected function makeIdKey($id, $toString = true) { + /** @var SelectQuery $query */ + $query = + $this->dao-> + makeSelectHead()-> + andWhere( + Expression::eq( + DBField::create( + $this->dao->getIdName(), + $this->dao->getTable() + ), + $id + ) + ); + if ($toString) { + return $this->makeQueryKey($query, self::SUFFIX_ITEM); + } else { + return $query; + } + } + + /** + * @return $this + */ + private function setSuffixItem() { + $this->suffix = self::SUFFIX_ITEM; + return $this; + } + + /** + * @return $this + */ + private function setSuffixList() { + $this->suffix = self::SUFFIX_LIST; + return $this; + } + + /** + * @return $this + */ + private function setSuffixCustom() { + $this->suffix = self::SUFFIX_CUSTOM; + return $this; + } + + protected function getSuffixQuery() { + return $this->suffix; + } + + public function getByQuery(SelectQuery $query, $expires = Cache::DO_NOT_CACHE) { + $this->setSuffixItem(); + return parent::getByQuery($query, $expires); + } + + public function getCustom(SelectQuery $query, $expires = Cache::DO_NOT_CACHE) { + $this->setSuffixCustom(); + return parent::getCustom($query, $expires); + } + + public function getListByQuery(SelectQuery $query, $expires = Cache::DO_NOT_CACHE) { + $this->setSuffixList(); + return parent::getListByQuery($query, $expires); + } + + public function getCustomList(SelectQuery $query, $expires = Cache::DO_NOT_CACHE) { + $this->setSuffixCustom(); + return parent::getCustomList($query, $expires); + } + + public function getCustomRowList(SelectQuery $query, $expires = Cache::DO_NOT_CACHE) { + $this->setSuffixCustom(); + return parent::getCustomRowList($query, $expires); + } + + public function getQueryResult(SelectQuery $query, $expires = Cache::DO_NOT_CACHE) { + $this->setSuffixCustom(); + return parent::getQueryResult($query, $expires); + } + + public function uncacheByIds($ids) { + $keys = array(); + foreach ($ids as $id) { + $keys[] = $this->makeIdKey($id); + } + + $isUncacheIds = Cache::me() + ->mark($this->className) + ->deleteList($keys) + ; + + return $isUncacheIds && $this->dao->uncacheLists(); + } + + public function uncacheLists() { + return Cache::me() + ->mark($this->className) + ->deleteByPattern($this->className . self::SUFFIX_LIST) + ; + } + + public function uncacheItems() { + return Cache::me() + ->mark($this->className) + ->deleteByPattern($this->className . self::SUFFIX_ITEM) + ; + } + + protected function cacheByQuery( + SelectQuery $query, + /* Identifiable */ $object, + $expires = Cache::DO_NOT_CACHE + ) + { + if ($expires !== Cache::DO_NOT_CACHE) { + + if (self::SUFFIX_ITEM == $this->getSuffixQuery()) { + + $idKey = $this->makeIdKey($object->getId()); + $queryKey = $this->makeQueryKey($query, $this->getSuffixQuery()); + + if ($idKey != $queryKey) { + Cache::me() + ->mark($this->className) + ->add( + $queryKey, + CacheLink::create() + ->setKey($idKey), + $expires + ); + } + + Cache::me() + ->mark($this->className) + ->add( + $idKey, + $object, + $expires + ); + } else if (self::SUFFIX_LIST == $this->getSuffixQuery()) { + /** @var CacheListLink $link */ + $link = CacheListLink::create(); + foreach ($object as $item) { + $idKey = $this->makeIdKey($item->getId()); + + Cache::me() + ->mark($this->className) + ->add( + $idKey, + $item, + $expires + ); + + $link->setKey($item->getId(), $idKey); + } + + parent::cacheByQuery($query, $link, $expires); + } else { + parent::cacheByQuery($query, $object, $expires); + } + } + + return $object; + } + + protected function getCachedByQuery(SelectQuery $query) + { + $object = Cache::me() + ->mark($this->className) + ->get( + $this->makeQueryKey( + $query, + $this->getSuffixQuery() + ) + ) + ; + + if ($object instanceof CacheLink) { + $object = Cache::me()->get($object->getKey()); + } else if ($object instanceof CacheListLink) { + + $keys = $object->getKeys(); + $object = Cache::me()->getList($keys); + + foreach ($keys as $id => $key) { + if (!$object[$key]) { + try { + $item = $this->dao->getById($id); + $object[$key] = $item; + Cache::me() + ->mark($this->className) + ->add( + $this->makeIdKey($id), + $item, + Cache::EXPIRES_MEDIUM + ); + } catch (ObjectNotFoundException $e) { + unset($object[$key]); + } + } + } + + $object = array_values($object); + } + + return $object; + } +} \ No newline at end of file diff --git a/main/DAOs/Workers/NullDaoWorker.class.php b/main/DAOs/Workers/NullDaoWorker.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/Workers/SmartDaoWorker.class.php b/main/DAOs/Workers/SmartDaoWorker.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/Workers/TransparentDaoWorker.class.php b/main/DAOs/Workers/TransparentDaoWorker.class.php old mode 100644 new mode 100755 diff --git a/main/DAOs/Workers/TrickyDaoWorker.class.php b/main/DAOs/Workers/TrickyDaoWorker.class.php new file mode 100755 index 0000000000..b1187368c9 --- /dev/null +++ b/main/DAOs/Workers/TrickyDaoWorker.class.php @@ -0,0 +1,37 @@ +isNoSqlDao() ) { + return true; + } + + return parent::uncacheLists(); + } + + /** + * Проверяем является ли текущий DAO реализацией NoSqlDAO + * @return bool + */ + protected function isNoSqlDao() { + return ($this->dao instanceof NoSqlDAO); + } + + + } diff --git a/main/DAOs/Workers/VoodooDaoWorker.class.php b/main/DAOs/Workers/VoodooDaoWorker.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Accessors/DTOGetter.class.php b/main/EntityProto/Accessors/DTOGetter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Accessors/DTOSetter.class.php b/main/EntityProto/Accessors/DTOSetter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Accessors/DirectoryGetter.class.php b/main/EntityProto/Accessors/DirectoryGetter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Accessors/DirectoryMutator.class.php b/main/EntityProto/Accessors/DirectoryMutator.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Accessors/DirectorySetter.class.php b/main/EntityProto/Accessors/DirectorySetter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Accessors/FormExporter.class.php b/main/EntityProto/Accessors/FormExporter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Accessors/FormGetter.class.php b/main/EntityProto/Accessors/FormGetter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Accessors/FormHardenedSetter.class.php b/main/EntityProto/Accessors/FormHardenedSetter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Accessors/FormImporter.class.php b/main/EntityProto/Accessors/FormImporter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Accessors/FormMutator.class.php b/main/EntityProto/Accessors/FormMutator.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Accessors/FormSetter.class.php b/main/EntityProto/Accessors/FormSetter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Accessors/ObjectGetter.class.php b/main/EntityProto/Accessors/ObjectGetter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Accessors/ObjectSetter.class.php b/main/EntityProto/Accessors/ObjectSetter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Accessors/ScopeGetter.class.php b/main/EntityProto/Accessors/ScopeGetter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Accessors/ScopeSetter.class.php b/main/EntityProto/Accessors/ScopeSetter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Builders/DTOBuilder.class.php b/main/EntityProto/Builders/DTOBuilder.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Builders/DTOToFormImporter.class.php b/main/EntityProto/Builders/DTOToFormImporter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Builders/DTOToScopeConverter.class.php b/main/EntityProto/Builders/DTOToScopeConverter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Builders/DirectoryBuilder.class.php b/main/EntityProto/Builders/DirectoryBuilder.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Builders/DirectoryToObjectBinder.class.php b/main/EntityProto/Builders/DirectoryToObjectBinder.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Builders/FormBuilder.class.php b/main/EntityProto/Builders/FormBuilder.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Builders/FormToObjectConverter.class.php b/main/EntityProto/Builders/FormToObjectConverter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Builders/FormToScopeExporter.class.php b/main/EntityProto/Builders/FormToScopeExporter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Builders/ObjectBuilder.class.php b/main/EntityProto/Builders/ObjectBuilder.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Builders/ObjectToDTOConverter.class.php b/main/EntityProto/Builders/ObjectToDTOConverter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Builders/ObjectToDirectoryBinder.class.php b/main/EntityProto/Builders/ObjectToDirectoryBinder.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Builders/ObjectToFormConverter.class.php b/main/EntityProto/Builders/ObjectToFormConverter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Builders/ObjectToFormSetter.class.php b/main/EntityProto/Builders/ObjectToFormSetter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Builders/ObjectToObjectCast.class.php b/main/EntityProto/Builders/ObjectToObjectCast.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/Builders/ScopeToFormImporter.class.php b/main/EntityProto/Builders/ScopeToFormImporter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/DirectoryContext.class.php b/main/EntityProto/DirectoryContext.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/EntityProto.class.php b/main/EntityProto/EntityProto.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/PrototypedBuilder.class.php b/main/EntityProto/PrototypedBuilder.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/PrototypedEntity.class.php b/main/EntityProto/PrototypedEntity.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/PrototypedGetter.class.php b/main/EntityProto/PrototypedGetter.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/PrototypedMethodCaller.class.php b/main/EntityProto/PrototypedMethodCaller.class.php old mode 100644 new mode 100755 diff --git a/main/EntityProto/PrototypedSetter.class.php b/main/EntityProto/PrototypedSetter.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/AddCommand.class.php b/main/Flow/AddCommand.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/BaseEditor.class.php b/main/Flow/BaseEditor.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/CarefulCommand.class.php b/main/Flow/CarefulCommand.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/CarefulDatabaseRunner.class.php b/main/Flow/CarefulDatabaseRunner.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/CommandChain.class.php b/main/Flow/CommandChain.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/Controller.class.php b/main/Flow/Controller.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/ControllersCollection.class.php b/main/Flow/ControllersCollection.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/DecoratorController.class.php b/main/Flow/DecoratorController.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/DropCommand.class.php b/main/Flow/DropCommand.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/EditCommand.class.php b/main/Flow/EditCommand.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/EditorCommand.class.php b/main/Flow/EditorCommand.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/EditorController.class.php b/main/Flow/EditorController.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/ForbiddenCommand.class.php b/main/Flow/ForbiddenCommand.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/HandlerMapping.class.php b/main/Flow/HandlerMapping.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/HttpRequest.class.php b/main/Flow/HttpRequest.class.php old mode 100644 new mode 100755 index 42b1eb25a6..bbb02ab43e --- a/main/Flow/HttpRequest.class.php +++ b/main/Flow/HttpRequest.class.php @@ -82,7 +82,17 @@ public function setGetVar($name, $value) $this->get[$name] = $value; return $this; } - + + /** + * @return HttpRequest + **/ + public function setGetString($get) + { + $this->get = $get; + + return $this; + } + public function &getPost() { return $this->post; @@ -116,7 +126,17 @@ public function setPostVar($name, $value) $this->post[$name] = $value; return $this; } - + + /** + * @return HttpRequest + **/ + public function setPostString($post) + { + $this->post = $post; + + return $this; + } + public function &getServer() { return $this->server; diff --git a/main/Flow/ImportCommand.class.php b/main/Flow/ImportCommand.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/MakeCommand.class.php b/main/Flow/MakeCommand.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/MethodMappedController.class.php b/main/Flow/MethodMappedController.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/Model.class.php b/main/Flow/Model.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/ModelAndView.class.php b/main/Flow/ModelAndView.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/MonolithicController.class.php b/main/Flow/MonolithicController.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/NullController.class.php b/main/Flow/NullController.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/PrototypedEditor.class.php b/main/Flow/PrototypedEditor.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/ProxyController.class.php b/main/Flow/ProxyController.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/SaveCommand.class.php b/main/Flow/SaveCommand.class.php old mode 100644 new mode 100755 diff --git a/main/Flow/TakeCommand.class.php b/main/Flow/TakeCommand.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Csv.class.php b/main/Markup/Csv.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/AtomChannelWorker.class.php b/main/Markup/Feed/AtomChannelWorker.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/AtomFeedFormat.class.php b/main/Markup/Feed/AtomFeedFormat.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/AtomItemWorker.class.php b/main/Markup/Feed/AtomItemWorker.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/FeedChannel.class.php b/main/Markup/Feed/FeedChannel.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/FeedChannelWorker.class.php b/main/Markup/Feed/FeedChannelWorker.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/FeedFormat.class.php b/main/Markup/Feed/FeedFormat.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/FeedItem.class.php b/main/Markup/Feed/FeedItem.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/FeedItemContent.class.php b/main/Markup/Feed/FeedItemContent.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/FeedItemContentType.class.php b/main/Markup/Feed/FeedItemContentType.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/FeedItemWorker.class.php b/main/Markup/Feed/FeedItemWorker.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/FeedReader.class.php b/main/Markup/Feed/FeedReader.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/RssChannelWorker.class.php b/main/Markup/Feed/RssChannelWorker.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/RssFeedFormat.class.php b/main/Markup/Feed/RssFeedFormat.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/RssItemWorker.class.php b/main/Markup/Feed/RssItemWorker.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/YandexRssFeedFormat.class.php b/main/Markup/Feed/YandexRssFeedFormat.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/YandexRssFeedItem.class.php b/main/Markup/Feed/YandexRssFeedItem.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Feed/YandexRssItemWorker.class.php b/main/Markup/Feed/YandexRssItemWorker.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Html/Cdata.class.php b/main/Markup/Html/Cdata.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Html/HtmlAssembler.class.php b/main/Markup/Html/HtmlAssembler.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Html/HtmlTokenizer.class.php b/main/Markup/Html/HtmlTokenizer.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Html/SgmlEndTag.class.php b/main/Markup/Html/SgmlEndTag.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Html/SgmlIgnoredTag.class.php b/main/Markup/Html/SgmlIgnoredTag.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Html/SgmlOpenTag.class.php b/main/Markup/Html/SgmlOpenTag.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Html/SgmlTag.class.php b/main/Markup/Html/SgmlTag.class.php old mode 100644 new mode 100755 diff --git a/main/Markup/Html/SgmlToken.class.php b/main/Markup/Html/SgmlToken.class.php old mode 100644 new mode 100755 diff --git a/main/Math/BigInteger.class.php b/main/Math/BigInteger.class.php old mode 100644 new mode 100755 diff --git a/main/Math/BigNumberFactory.class.php b/main/Math/BigNumberFactory.class.php old mode 100644 new mode 100755 diff --git a/main/Math/Calculator.class.php b/main/Math/Calculator.class.php new file mode 100644 index 0000000000..67df124c91 --- /dev/null +++ b/main/Math/Calculator.class.php @@ -0,0 +1,204 @@ + + * @author Alex Gorbylev + * @date 2011.12.23 + */ +final class Calculator { + + const OP_SUMMARIZE = 1; + const OP_MULTIPLY = 2; + const OP_PRIORITY = 3; + + // массив значений + protected $variables = array(); + + // Массив операций в строке + protected $formula; + + // Номер текущей операции в строке + protected $index; + + // Стек значений в зависимости от последовательности операций + protected $stack; + + // результат по умолчанию + protected $default = 0; + + protected $operators = array( + '+' => self::OP_SUMMARIZE, + '-' => self::OP_SUMMARIZE, + + '*' => self::OP_MULTIPLY, + '/' => self::OP_MULTIPLY, + '%' => self::OP_MULTIPLY, + + '(' => self::OP_PRIORITY, + ); + + final protected function __construct() {/*_*/} + + /** + * @param string $formula + * @param array $input + * @return int + */ + public static function execute($formula, array $input = null) { + $calc = new self(); + $input = is_array($input) && !empty($input) ? $input : array(); + // запускаем! + return $calc->calculate($formula, $input ); + } + + /** + * Расчет конечного результата в зависимости от условия + */ + public function calculate( $math, array $input ) { + $this->formula = array(); + $this->stack = array(); + + $math = strtolower( $math ); + // проверяем на только допустимые символы + if ( preg_replace( '/[0-9\(\)\s\+\-\*\/\%\$\.]+/', '', $math ) !== '' ) { + throw new WrongArgumentException('Formula contains unsupported symbols'); + } + // делаем по пробелу между элементами + $math = preg_replace('/\s+/', '', $math); + $math = preg_replace( '/([\(\)\+\-\*\/\%]{1})/', ' $1 ', $math ); + $math = trim( preg_replace('/\s+/', ' ', $math) ); + // разбиваем формулу на составные части + $this->formula = explode( ' ', $math ); + + // Инициализация + $this->variables = $input; + $this->index = 0; + + $this->calculateSummarize(); + return + isset( $this->stack[0] ) + ? $this->stack[0] + : $this->default + ; + } + + /** + * Рассчитывает операцию + */ + protected function evaluateVariables( $operation ) { + $p2 = array_pop ( $this->stack ); + $p1 = array_pop ( $this->stack ); + + $res = $this->default; + switch($operation) { + case '+': { + $res = $p1 + $p2; + } break; + case '-': { + $res = $p1 - $p2; + } break; + case '*': { + $res = $p1 * $p2; + } break; + case '/': { + $res = $p1 / $p2; + } break; + case '%': { + $res = $p1 % $p2; + } break; + default: { + throw new WrongArgumentException("Unknown math operation '{$operation}'"); + } break; + } + + array_push( $this->stack, $res ); + } + + /** + * Расчет строки как формулы + */ + protected function calculateSummarize() { + $this->calculateMultiply(); + + if($this->checkStop()){ return; } + + if ( $this->getCurrentOperatorClass() == self::OP_SUMMARIZE ) { + $operator = $this->getCurrent(); + $this->next(); + $this->calculateSummarize(); + $this->evaluateVariables( $operator ); + return; + } + } + + /** + * Расчет множителей + */ + protected function calculateMultiply() { + $this->calculatePriority(); + + if($this->checkStop()){ return; } + + if ( $this->getCurrentOperatorClass() == self::OP_MULTIPLY ) { + $operator = $this->getCurrent(); + $this->next(); + $this->calculateMultiply(); + $this->evaluateVariables( $operator ); + return; + } + } + + /** + * Расчет скобок + */ + protected function calculatePriority() { + if ( $this->getCurrentOperatorClass() == self::OP_PRIORITY ) { + $this->next(); + $this->calculateSummarize(); + $this->next(); + } + else { + $this->calculateValue(); + } + } + + /** + * Оперирует числом в строке условия + */ + protected function calculateValue() { + // значение в строке + $operand = $this->getCurrent(); + + // получаем значение операнда + $value_to_push = $this->default; + // типа операнда: константа или переменная + if( $operand{0}=='$' ) { + $key = intval(substr($operand, 1))-1; + $value_to_push = array_key_exists($key, $this->variables) ? $this->variables[$key] : $this->default; + } elseif(Assert::checkFloat($operand)) { + $value_to_push = floatval($operand); + } + + // добавляем элемент в стек для последующей подстановки в выражение + array_push( $this->stack, $value_to_push ); + $this->next(); + } + + private function next() { + $this->index++; + } + + private function checkStop() { + return $this->index >= count( $this->formula ); + } + + private function getCurrent() { + return $this->formula[$this->index]; + } + + private function getCurrentOperatorClass() { + $exist = isset( $this->operators[ $this->getCurrent() ] ); + return $exist ? $this->operators[ $this->getCurrent() ] : null; + } + +} \ No newline at end of file diff --git a/main/Math/FileRandomSource.class.php b/main/Math/FileRandomSource.class.php old mode 100644 new mode 100755 diff --git a/main/Math/GmpBigInteger.class.php b/main/Math/GmpBigInteger.class.php old mode 100644 new mode 100755 diff --git a/main/Math/GmpBigIntegerFactory.class.php b/main/Math/GmpBigIntegerFactory.class.php old mode 100644 new mode 100755 diff --git a/main/Math/MathUtils.class.php b/main/Math/MathUtils.class.php old mode 100644 new mode 100755 diff --git a/main/Math/MtRandomSource.class.php b/main/Math/MtRandomSource.class.php old mode 100644 new mode 100755 diff --git a/main/Math/RandomSource.class.php b/main/Math/RandomSource.class.php old mode 100644 new mode 100755 diff --git a/main/Math/TupleFunctor.class.php b/main/Math/TupleFunctor.class.php old mode 100644 new mode 100755 diff --git a/main/Messages/Interface/Message.class.php b/main/Messages/Interface/Message.class.php old mode 100644 new mode 100755 diff --git a/main/Messages/Interface/MessageQueue.class.php b/main/Messages/Interface/MessageQueue.class.php old mode 100644 new mode 100755 diff --git a/main/Messages/Interface/MessageQueueBrowser.class.php b/main/Messages/Interface/MessageQueueBrowser.class.php old mode 100644 new mode 100755 diff --git a/main/Messages/Interface/MessageQueueReceiver.class.php b/main/Messages/Interface/MessageQueueReceiver.class.php old mode 100644 new mode 100755 diff --git a/main/Messages/Interface/MessageQueueSender.class.php b/main/Messages/Interface/MessageQueueSender.class.php old mode 100644 new mode 100755 diff --git a/main/Messages/TextFileQueue.class.php b/main/Messages/TextFileQueue.class.php old mode 100644 new mode 100755 diff --git a/main/Messages/TextFileQueueBrowser.class.php b/main/Messages/TextFileQueueBrowser.class.php old mode 100644 new mode 100755 diff --git a/main/Messages/TextFileReceiver.class.php b/main/Messages/TextFileReceiver.class.php old mode 100644 new mode 100755 diff --git a/main/Messages/TextFileSender.class.php b/main/Messages/TextFileSender.class.php old mode 100644 new mode 100755 diff --git a/main/Messages/TextMessage.class.php b/main/Messages/TextMessage.class.php old mode 100644 new mode 100755 diff --git a/main/Monitoring/PinbaClient.class.php b/main/Monitoring/PinbaClient.class.php old mode 100644 new mode 100755 diff --git a/main/Monitoring/PinbedMemcached.class.php b/main/Monitoring/PinbedMemcached.class.php old mode 100644 new mode 100755 diff --git a/main/Monitoring/PinbedPeclMemcached.class.php b/main/Monitoring/PinbedPeclMemcached.class.php old mode 100644 new mode 100755 diff --git a/main/Monitoring/PinbedPgSQL.class.php b/main/Monitoring/PinbedPgSQL.class.php old mode 100644 new mode 100755 diff --git a/main/Net/GenericUri.class.php b/main/Net/GenericUri.class.php old mode 100644 new mode 100755 index 3f234ca030..ef531947e3 --- a/main/Net/GenericUri.class.php +++ b/main/Net/GenericUri.class.php @@ -285,6 +285,55 @@ public function getQuery() { return $this->query; } + + /** + * @return array|null + */ + public function getQueryArray() { + if ($this->getQuery()) { + parse_str($this->getQuery(), $array); + return $array; + } + return null; + } + + /** + * @param $array + * @return $this + */ + public function setQueryArray($array) { + Assert::isArray($array); + + $build = function ($array, $prefix = null) use (&$build) { + $pairs = array(); + foreach ($array as $key => $value) { + $key = urlencode($key); + if ($prefix) { + $key = $prefix . '[' . $key . ']'; + } + + if (is_object($value)) { + throw new WrongArgumentException($key . ' is an object (' . get_class($value) . ')'); + } + + if (is_array($value)) { + foreach ($build($value, $key) as $pair) { + $pairs []= $pair; + } + } else { + if (is_string($value)) $value = urlencode($value); + if (is_bool($value)) $value = $value ? '1' : '0'; + + $pairs []= $key . '=' . $value; + } + } + return $pairs; + }; + + $this->setQuery( implode('&', $build($array)) ); + + return $this; + } /** * @return GenericUri diff --git a/main/Net/Http/Cookie.class.php b/main/Net/Http/Cookie.class.php old mode 100644 new mode 100755 diff --git a/main/Net/Http/CookieCollection.class.php b/main/Net/Http/CookieCollection.class.php old mode 100644 new mode 100755 diff --git a/main/Net/Http/CurlHttpClient.class.php b/main/Net/Http/CurlHttpClient.class.php old mode 100644 new mode 100755 index 574371cd9c..be61aa856a --- a/main/Net/Http/CurlHttpClient.class.php +++ b/main/Net/Http/CurlHttpClient.class.php @@ -23,6 +23,8 @@ final class CurlHttpClient implements HttpClient private $multiRequests = array(); private $multiResponses = array(); private $multiThreadOptions = array(); + + private $handler = null; /** * @return CurlHttpClient @@ -61,7 +63,7 @@ public function getOption($key) } /** - * @param $timeout in seconds + * @param $timeout int seconds * @return CurlHttpClient **/ public function setTimeout($timeout) @@ -184,12 +186,12 @@ public function getResponse(HttpRequest $request) /** * @return HttpResponse **/ - public function send(HttpRequest $request) + public function send(HttpRequest $request, $keepHandler = false) { $response = CurlHttpResponse::create()-> setMaxFileSize($this->maxFileSize); - $handle = $this->makeHandle($request, $response); + $handle = $this->makeHandle($request, $response, $keepHandler); if (curl_exec($handle) === false) { $code = curl_errno($handle); @@ -201,8 +203,10 @@ public function send(HttpRequest $request) } $this->makeResponse($handle, $response); - - curl_close($handle); + + if( !$keepHandler ) { + curl_close($handle); + } return $response; } @@ -245,15 +249,31 @@ public function multiSend() return true; } + + public function close() { + if ($this->handler) { + curl_close($this->handler); + $this->handler = null; + } + return $this; + } protected function getRequestKey(HttpRequest $request) { return md5(serialize($request)); } - protected function makeHandle(HttpRequest $request, CurlHttpResponse $response) + protected function makeHandle(HttpRequest $request, CurlHttpResponse $response, $keepHandler = false) { - $handle = curl_init(); + if( $keepHandler && !is_null($this->handler) ) { + $handle = $this->handler; + } else { + $handle = curl_init(); + if( $keepHandler ) { + $this->handler = $handle; + } + } + Assert::isNotNull($request->getMethod()); $options = array( @@ -281,15 +301,17 @@ protected function makeHandle(HttpRequest $request, CurlHttpResponse $response) case HttpMethod::POST: $options[CURLOPT_POST] = true; - $options[CURLOPT_POSTFIELDS] = - $this->argumentsToString($request->getPost()); break; default: $options[CURLOPT_CUSTOMREQUEST] = $request->getMethod()->getName(); break; } - + + if ($request->getPost()) { + $options[CURLOPT_POSTFIELDS] = $this->argumentsToString($request->getPost()); + } + $headers = array(); foreach ($request->getHeaderList() as $headerName => $headerValue) { $headers[] = "{$headerName}: $headerValue"; @@ -339,21 +361,37 @@ protected function makeResponse($handle, CurlHttpResponse $response) private function argumentsToString($array) { - Assert::isArray($array); - $result = array(); - - foreach ($array as $key => $value) { - if (is_array($value)) { - foreach ($value as $valueKey => $simpleValue) { - $result[] = - $key.'['.$valueKey.']='.urlencode($simpleValue); + if( is_array($array) ) { + $build = function ($array, $prefix = null) use (&$build) { + $pairs = array(); + foreach ($array as $key => $value) { + $key = urlencode($key); + if ($prefix) { + $key = $prefix . '[' . $key . ']'; + } + + if (is_object($value)) { + throw new WrongArgumentException($key . ' is an object (' . get_class($value) . ')'); + } + + if (is_array($value)) { + foreach ($build($value, $key) as $pair) { + $pairs []= $pair; + } + } else { + if (is_string($value)) $value = urlencode($value); + if (is_bool($value)) $value = $value ? '1' : '0'; + + $pairs []= $key . '=' . $value; + } } - } else { - $result[] = $key.'='.urlencode($value); - } + return $pairs; + }; + + return implode('&', $build($array)); + } else { + return $array; } - - return implode('&', $result); } } ?> \ No newline at end of file diff --git a/main/Net/Http/CurlHttpResponse.class.php b/main/Net/Http/CurlHttpResponse.class.php old mode 100644 new mode 100755 diff --git a/main/Net/Http/HeaderParser.class.php b/main/Net/Http/HeaderParser.class.php old mode 100644 new mode 100755 diff --git a/main/Net/Http/HeaderUtils.class.php b/main/Net/Http/HeaderUtils.class.php old mode 100644 new mode 100755 diff --git a/main/Net/Http/HttpClient.class.php b/main/Net/Http/HttpClient.class.php old mode 100644 new mode 100755 diff --git a/main/Net/Http/HttpMethod.class.php b/main/Net/Http/HttpMethod.class.php old mode 100644 new mode 100755 index 736c720e3f..4fa08f9c06 --- a/main/Net/Http/HttpMethod.class.php +++ b/main/Net/Http/HttpMethod.class.php @@ -57,5 +57,15 @@ public static function post() { return new self(self::POST); } + + public static function put() + { + return new self(self::PUT); + } + + public static function delete() + { + return new self(self::DELETE); + } } ?> \ No newline at end of file diff --git a/main/Net/Http/HttpResponse.class.php b/main/Net/Http/HttpResponse.class.php old mode 100644 new mode 100755 diff --git a/main/Net/Http/HttpStatus.class.php b/main/Net/Http/HttpStatus.class.php old mode 100644 new mode 100755 diff --git a/main/Net/HttpUrl.class.php b/main/Net/HttpUrl.class.php old mode 100644 new mode 100755 diff --git a/main/Net/Ip/IpAddress.class.php b/main/Net/Ip/IpAddress.class.php old mode 100644 new mode 100755 diff --git a/main/Net/Ip/IpCidrRange.class.php b/main/Net/Ip/IpCidrRange.class.php new file mode 100644 index 0000000000..714fc9f818 --- /dev/null +++ b/main/Net/Ip/IpCidrRange.class.php @@ -0,0 +1,112 @@ + + * @date 2013.12.02 + */ + +/** + * @ingroup Ip + */ + +class IpCidrRange implements SingleRange, DialectString, Stringable { + + /** @var IpAddress */ + protected $startIp; + + /** @var IpAddress */ + protected $endIp; + + protected $mask; + + /** + * @return IpCidrRange + **/ + public static function create($string) + { + return new static($string); + } + + public function __construct( $string ) { + Assert::isString($string); + if ( preg_match( IpRange::SINGLE_IP_PATTERN, $string ) ) { + $ip = IpAddress::create($string); + $this->startIp = $ip; + $this->endIp = $ip; + $this->mask = 32; + } elseif ( preg_match( IpRange::IP_SLASH_PATTERN, $string ) ) { + list($ip, $mask) = explode('/', $string); + $this->createFromSlash($ip, $mask); + } elseif ( preg_match( IpRange::IP_WILDCARD_PATTERN, $string ) ) { + $ip = substr($string, 0, -2); + $mask = substr_count($string, '.') * 8; + $this->createFromSlash($ip, $mask); + + } else { + throw new WrongArgumentException('strange parameters received'); + } + } + + protected function createFromSlash($ip, $mask) + { + $ip = IpAddress::createFromCutted($ip); + + if($mask == 32) { + $this->startIp = $this->endIp = $ip; + $this->mask = $mask; + return; + } + + if ($mask == 0 || IpRange::MASK_MAX_SIZE < $mask) + throw new WrongArgumentException('wrong mask given'); + + $longMask = + (int) (pow(2, (32 - $mask)) * (pow(2, $mask) - 1)); + + if (($ip->getLongIp() & $longMask) != $ip->getLongIp()) + throw new WrongArgumentException('wrong ip network given'); + + $this->startIp = $ip; + $this->endIp = IpAddress::create( long2ip($ip->getLongIp() | ~$longMask) ); + $this->mask = $mask; + } + + public function toString() { + return $this->getStart()->toString() . '/' . $this->getMask(); + } + + public function toDialectString(Dialect $dialect) { + return $dialect->quoteValue($this->toString()); + } + + /** + * @return IpAddress + **/ + public function getStart() + { + return $this->startIp; + } + + /** + * @return IpAddress + **/ + public function getEnd() + { + return $this->endIp; + } + + public function getMask() { + return $this->mask; + } + + public function contains(/* IpAddress */ $probe) { + /** @var IpAddress $probe */ + Assert::isInstance($probe, 'IpAddress'); + + return ( + ($this->startIp->getLongIp() <= $probe->getLongIp()) + && ($this->endIp->getLongIp() >= $probe->getLongIp()) + ); + } + +} \ No newline at end of file diff --git a/main/Net/Ip/IpNetwork.class.php b/main/Net/Ip/IpNetwork.class.php old mode 100644 new mode 100755 diff --git a/main/Net/Ip/IpRange.class.php b/main/Net/Ip/IpRange.class.php old mode 100644 new mode 100755 index 9d23ef66b1..661be8cee8 --- a/main/Net/Ip/IpRange.class.php +++ b/main/Net/Ip/IpRange.class.php @@ -19,7 +19,8 @@ class IpRange implements SingleRange, DialectString, Stringable const SINGLE_IP_PATTERN = '/^(\d{1,3}\.){3}\d{1,3}$/'; const INTERVAL_PATTERN = '/^\d{1,3}(\.\d{1,3}){3}\s*-\s*\d{1,3}(\.\d{1,3}){3}$/'; const IP_SLASH_PATTERN = '/^(\d{1,3}\.){0,3}\d{1,3}\/\d{1,2}$/'; - + const IP_WILDCARD_PATTERN = '/^(\d{1,3}\.){0,3}\*$/'; + private $startIp = null; private $endIp = null; @@ -138,6 +139,11 @@ private function createFromInterval($interval) private function createFromSlash($ip, $mask) { $ip = IpAddress::createFromCutted($ip); + + if($mask == 32) { + $this->setup($ip, $ip); + return; + } if ($mask == 0 || self::MASK_MAX_SIZE < $mask) throw new WrongArgumentException('wrong mask given'); @@ -166,10 +172,17 @@ private function createFromString($string) list($ip, $mask) = explode('/', $string); $this->createFromSlash($ip, $mask); - } elseif (preg_match(self::INTERVAL_PATTERN, $string)) - $this->createFromInterval($string); - else + } elseif (preg_match(self::INTERVAL_PATTERN, $string)) { + $this->createFromInterval($string); + + } elseif (preg_match(self::IP_WILDCARD_PATTERN, $string)) { + $ip = substr($string, 0, -2); + $mask = substr_count($string, '.') * 8; + $this->createFromSlash($ip, $mask); + + } else throw new WrongArgumentException('strange parameters received'); } + } ?> \ No newline at end of file diff --git a/main/Net/Ip/IpUtils.class.php b/main/Net/Ip/IpUtils.class.php old mode 100644 new mode 100755 index 4a9d5d5cfe..8cfc268229 --- a/main/Net/Ip/IpUtils.class.php +++ b/main/Net/Ip/IpUtils.class.php @@ -40,5 +40,21 @@ public static function makeRanges(array $ips) return $ranges; } + + public static function checkCIDR($ip, $cidr) { + if( !preg_match('@(\d+\.\d+\.\d+\.\d+)(?:/(\d+))?@mi', $cidr, $matches) ) { + throw new WrongArgumentException('CIDR is not valid'); + } + $net = $matches[1]; + $mask = isset($matches[2]) ? $matches[2] : 32; + // 2 long + $int_ip = ip2long($ip); + $int_net = ip2long($net); + $int_mask = ~((1 << (32 - $mask)) - 1); + // bitwise AND + $ip_mask = $int_ip & $int_mask; + // check + return ($ip_mask == $int_net); + } } ?> \ No newline at end of file diff --git a/main/Net/Mail/Mail.class.php b/main/Net/Mail/Mail.class.php old mode 100644 new mode 100755 index a3530783bd..a26fd1af33 --- a/main/Net/Mail/Mail.class.php +++ b/main/Net/Mail/Mail.class.php @@ -27,8 +27,9 @@ final class Mail private $contentType = null; private $returnPath = null; + private $sendmailAdditionalHeaders = null; private $sendmailAdditionalArgs = null; - + /** * @return Mail **/ @@ -114,6 +115,10 @@ public function send() $headers .= "Content-Transfer-Encoding: 8bit\n"; $headers .= "Date: ".date('r')."\n"; + + if($this->sendmailAdditionalHeaders != null) { + $headers .= $this->sendmailAdditionalHeaders."\n"; + } if ( !mail( @@ -221,5 +226,20 @@ public function setReturnPath($returnPath) $this->returnPath = $returnPath; return $this; } + + public function getSendmailAdditionalHeaders() + { + return $this->sendmailAdditionalHeaders; + } + + /** + * @return Mail + **/ + public function setSendmailAdditionalHeaders($sendmailAdditionalHeaders) + { + $this->sendmailAdditionalHeaders = $sendmailAdditionalHeaders; + return $this; + } + } ?> \ No newline at end of file diff --git a/main/Net/Mail/MailAddress.class.php b/main/Net/Mail/MailAddress.class.php old mode 100644 new mode 100755 diff --git a/main/Net/Mail/MailBuilder.class.php b/main/Net/Mail/MailBuilder.class.php old mode 100644 new mode 100755 diff --git a/main/Net/Mail/MailEncoding.class.php b/main/Net/Mail/MailEncoding.class.php old mode 100644 new mode 100755 diff --git a/main/Net/Mail/MailException.class.php b/main/Net/Mail/MailException.class.php old mode 100644 new mode 100755 diff --git a/main/Net/Mail/MailNotSentException.class.php b/main/Net/Mail/MailNotSentException.class.php old mode 100644 new mode 100755 diff --git a/main/Net/Mail/MimeMail.class.php b/main/Net/Mail/MimeMail.class.php old mode 100644 new mode 100755 index bfeb2caf9f..e034bde37b --- a/main/Net/Mail/MimeMail.class.php +++ b/main/Net/Mail/MimeMail.class.php @@ -23,6 +23,8 @@ final class MimeMail implements MailBuilder private $headers = null; private $boundary = null; + + private $contentType= null; /** * @return MimeMail @@ -40,11 +42,11 @@ public function build() throw new UnimplementedFeatureException(); if (!$this->boundary) - $this->boundary = '=_'.md5(microtime(true)); - + $this->boundary = '=_'.md5(microtime(true) . uniqid()); + $mail = MimePart::create()-> - setContentType('multipart/mixed')-> + setContentType(is_null($this->contentType)?'multipart/mixed':$this->contentType)-> setBoundary($this->boundary); $this->headers = @@ -57,7 +59,7 @@ public function build() .$part->getHeaders() ."\n\n" .$part->getEncodedBody()."\n"; - + $this->body .= '--'.$this->boundary."--"."\n\n"; } @@ -93,5 +95,13 @@ public function getBoundary() { return $this->boundary; } + + public function setContentType($type) { + $this->contentType = $type; + + return $this; + } + + } ?> \ No newline at end of file diff --git a/main/Net/Mail/MimePart.class.php b/main/Net/Mail/MimePart.class.php old mode 100644 new mode 100755 index f0e808b0c2..6413c81e8d --- a/main/Net/Mail/MimePart.class.php +++ b/main/Net/Mail/MimePart.class.php @@ -19,7 +19,7 @@ final class MimePart implements MailBuilder private $contentId = null; private $contentType = null; private $boundary = null; - + private $encoding = null; private $charset = null; @@ -63,7 +63,7 @@ public function getBoundary() { return $this->boundary; } - + public function getContentId() { return $this->contentId; @@ -223,10 +223,19 @@ public function getEncodedBody() **/ case MailEncoding::QUOTED: +// $string = +// preg_replace( +// '/[^\x21-\x3C\x3E-\x7E\x09\x20]/e', +// 'sprintf("=%02x", ord ("$0"));', +// $this->body +// ); $string = - preg_replace( - '/[^\x21-\x3C\x3E-\x7E\x09\x20]/e', - 'sprintf("=%02x", ord ("$0"));', + preg_replace_callback( + '/[^\x21-\x3C\x3E-\x7E\x09\x20]/', + function($value){ + $symbol = array_shift($value); + return sprintf("=%02x", ord($symbol)); + }, $this->body ); @@ -254,14 +263,35 @@ public function getEncodedBody() default: throw new WrongStateException('unknown mail encoding given'); } - + + if ($this->parts) { + if (!$this->boundary) { + throw new UnexpectedValueException('set boundary or call getHeader() first'); + } + + foreach ($this->parts as $part) { + /** @var $part MimePart */ + $body .= + '--' . $this->boundary . "\n" + . $part->getHeaders() . "\n\n" + . $part->getEncodedBody() . "\n\n" + ; + } + + $this->body .= '--'.$this->boundary."--"."\n"; + } + return $body; } - + public function getHeaders() { $headers = array(); - + + if ($this->parts && !$this->boundary) { + $this->boundary = '=_'.md5(microtime(true) . uniqid()); + } + if ($this->contentType) { $header = "Content-Type: {$this->contentType};"; @@ -271,7 +301,7 @@ public function getHeaders() if ($this->boundary) $header .= "\n\tboundary=\"{$this->boundary}\""; - + $headers[] = $header; } diff --git a/main/Net/Mail/SmtpTransport.class.php b/main/Net/Mail/SmtpTransport.class.php new file mode 100644 index 0000000000..7bb61782e1 --- /dev/null +++ b/main/Net/Mail/SmtpTransport.class.php @@ -0,0 +1,906 @@ + + * @date 2013.07.17 + */ + +class SmtpTransport { + + const DEFAULT_PORT = 25; + + const DEBUG_DISABLED = 0; + const DEBUG_MINIMUM = 10; + const DEBUG_MEDIUM = 20; + const DEBUG_MAXIMUM = 30; + const DEBUG_TOTAL = 40; + + protected $useSSL = false; + + protected $useTLS = false; + + /** @var string */ + protected $serverName = 'localhost'; + + /** @var string */ + protected $authMethod = 'LOGIN'; + + /** @var int */ + protected $streamTimeout = 30; + + /** @var int */ + protected $operationTimeLimit = 15; + + /** @var string */ + protected $crlf = "\r\n"; + + /** @var bool */ + protected $useVerp = false; + + /** @var int */ + protected $debugLevel = 0; + + /** @var Closure */ + protected $debugWriter = null; + + /** @var resource */ + protected $connection = null; + + /** @var string */ + protected $serverAuthMethods = null; + + /** @var string */ + protected $lastRecipient = null; + + /** + * @return SmtpTransport + */ + public static function create() { + return new self; + } + + protected function __construct() { + $this->debugWriter = function($message){ + echo $message."\n"; + }; + } + + /** + * @return boolean + */ + public function isUseSSL() { + return $this->useSSL; + } + + /** + * @param boolean $useSsl + * @return SmtpTransport + */ + public function setUseSSL($useSsl) { + $this->useSSL = $useSsl; + return $this; + } + + /** + * @return boolean + */ + public function isUseTLS() { + return $this->useTLS; + } + + /** + * @param boolean $useTls + * @return SmtpTransport + */ + public function setUseTLS($useTls) { + $this->useTLS = $useTls; + return $this; + } + + /** + * @return string + */ + public function getServerName() { + return $this->serverName; + } + + /** + * @param string $serverName + * @return SmtpTransport + */ + public function setServerName($serverName) { + $this->serverName = $serverName; + return $this; + } + + /** + * @return SmtpTransport + */ + public function setAuthMethodPlain() { + $this->authMethod = 'PLAIN'; + return $this; + } + + /** + * @return SmtpTransport + */ + public function setAuthMethodLogin() { + $this->authMethod = 'LOGIN'; + return $this; + } + + /** + * @return SmtpTransport + */ + public function setAuthMethodCramMD5() { + $this->authMethod = 'CRAM-MD5'; + return $this; + } + + /** + * @return int + */ + public function getStreamTimeout() { + return $this->streamTimeout; + } + + /** + * Sets the SMTP timeout value for socket, in seconds + * @param int $streamTimeout + * @return SmtpTransport + */ + public function setStreamTimeout($streamTimeout) { + $this->streamTimeout = $streamTimeout; + return $this; + } + + /** + * @return int + */ + public function getOperationTimeLimit() { + return $this->operationTimeLimit; + } + + /** + * Sets the SMTP timelimit value for reads, in seconds + * @param int $operationTimeLimit + * @return SmtpTransport + */ + public function setOperationTimeLimit($operationTimeLimit) { + $this->operationTimeLimit = $operationTimeLimit; + return $this; + + } + + /** + * @return string + */ + public function getCrlf() { + return $this->crlf; + } + + /** + * SMTP reply line ending + * @param string $crlf + * @return SmtpTransport + */ + public function setCrlf($crlf) { + $this->crlf = $crlf; + return $this; + } + + /** + * @return boolean + */ + public function isUseVerp() { + return (bool)$this->useVerp; + } + + /** + * Sets VERP use on/off + * @param boolean $useVerp + * @return SmtpTransport + */ + public function setUseVerp($useVerp) { + $this->useVerp = $useVerp; + return $this; + } + + /** + * @return SmtpTransport + */ + public function debugDisabled() { + $this->debugLevel = self::DEBUG_DISABLED; + return $this; + } + + /** + * @return SmtpTransport + */ + public function debugMinimum() { + $this->debugLevel = self::DEBUG_MINIMUM; + return $this; + } + + /** + * @return SmtpTransport + */ + public function debugMedium() { + $this->debugLevel = self::DEBUG_MEDIUM; + return $this; + } + + /** + * @return SmtpTransport + */ + public function debugMaximum() { + $this->debugLevel = self::DEBUG_MAXIMUM; + return $this; + } + + /** + * @return SmtpTransport + */ + public function debugTotal() { + $this->debugLevel = self::DEBUG_TOTAL; + return $this; + } + + /** + * @return callable + */ + public function getDebugWriter() { + return $this->debugWriter; + } + + /** + * @param callable $debugWriter + * @return SmtpTransport + */ + public function setDebugWriter($debugWriter) { + $this->debugWriter = $debugWriter; + return $this; + } + +// public function sendMail(Mail $mail) { +// +// } +// +// public function sendMimeMail(MimeMail $mail) { +// +// } +// +// public function sendRawMail($to, $subject, $message, $additional_headers = null, $additional_parameters = null) { +// +// } + + //===== common functions + + /** + * Connect to an SMTP server + * + * SMTP CODE SUCCESS: 220 + * SMTP CODE FAILURE: 421 + * @param string $host SMTP server IP or host name + * @param int $port The port number to connect to, or use the default port if not specified + * @param array $options An array of options compatible with stream_context_create() + * @throws SmtpTransportException + * @return SmtpTransport + */ + public function connect($host, $port = null, $options = array()) { + // Make sure we are __not__ connected + if($this->isConnected()) { + return $this; + } + + if(empty($port)) { + $port = self::DEFAULT_PORT; + } + + if($this->isUseSsl()) { + $host = 'ssl://'.$host; + } + + // Connect to the SMTP server + $errno = 0; + $errstr = ''; + $socket_context = stream_context_create($options); + //Need to suppress errors here as connection failures can be handled at a higher level + $this->connection = @stream_socket_client($host.":".$port, $errno, $errstr, $this->streamTimeout, STREAM_CLIENT_CONNECT, $socket_context); + + // Verify we connected properly + if(empty($this->connection)) { + $this->debugOut("SMTP->ERROR: Failed to connect to server: $errstr ($errno)", self::DEBUG_MINIMUM); + throw new SmtpTransportException('Failed to connect to server'); + } + + // SMTP server can take longer to respond, give longer timeout for first read + // Windows does not have support for this timeout function + if(substr(PHP_OS, 0, 3) != 'WIN') { + $max = ini_get('max_execution_time'); + if ($max != 0 && $this->streamTimeout > $max) { // Don't bother if unlimited + @set_time_limit($this->streamTimeout); + } + stream_set_timeout($this->connection, $this->streamTimeout, 0); + } + + // get any announcement + $announce = $this->execCommand(null, 220); + + $this->debugOut('SMTP: connected :: ' . $announce['payload'], self::DEBUG_MINIMUM); + + return $this; + } + + /** + * Sends the HELO command to the smtp server. + * This makes sure that we and the server are in + * the same known state. + * + * Implements from rfc 821: HELO + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE ERROR : 500, 501, 504, 421 + * + * @throws SmtpTransportException + * @throws WrongStateException + * @return SmtpTransport + */ + public function hello() { + $this->assertConnection(__METHOD__); + + // if hostname for HELO was not specified send default + if(empty($this->serverName)) { + // determine appropriate default to send to server + $this->serverName = 'localhost'; + } + + $helloSent = false; + + // Send extended hello first (RFC 2821) + try { + $hello = $this->execCommand('EHLO ' . $this->serverName, 250); + $helloSent = true; + } catch(SmtpTransportException $e) { + // skip... + } + // Send common hello + if( !$helloSent ) { + try { + $hello = $this->execCommand('HELO ' . $this->serverName, 250); + $helloSent = true; + } catch(SmtpTransportException $e) { + // skip... + } + } + // Check hello result + if( !$helloSent ) { + $this->debugOut("SMTP->ERROR: {$hello['code']}: {$hello['payload']}"); + throw new SmtpTransportException('EHLO/HELO not accepted'); + } + // Parse available auth methods + $authString = substr($hello['payload'], 5, strpos($hello['payload'], "\n")-6); + $this->serverAuthMethods = explode(' ', $authString); + + $this->debugOut('SMTP: hello passed', self::DEBUG_MEDIUM); + + return $this; + } + + /** + * Initiate a TLS communication with the server. + * + * SMTP CODE 220 Ready to start TLS + * SMTP CODE 501 Syntax error (no parameters allowed) + * SMTP CODE 454 TLS not available due to temporary reason + * @throws SmtpTransportException + * @throws WrongStateException + * @return SmtpTransport + */ + public function startTLS() { + $this->assertConnection(__METHOD__); + + $initCrypto = false; + try { + $this->execCommand('STARTTLS', 220); + $initCrypto = true; + } catch(SmtpTransportException $e) { + // skip... + } + + if( $initCrypto ) { + // Begin encrypted connection + if(!stream_socket_enable_crypto($this->connection, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { + throw new SmtpTransportException('Could not init TLS on socket level'); + } + } else { + $this->debugOut('STARTTLS not accepted from server', self::DEBUG_MEDIUM); + } + + $this->debugOut('SMTP: StarTLS started', self::DEBUG_MEDIUM); + + return $this; + } + + /** + * Performs SMTP authentication. Must be run after running the + * Hello() method. + * @param string $username + * @param string $password + * @throws SmtpTransportException + * @throws WrongStateException + * @return SmtpTransport + */ + public function authenticate($username, $password) { + $this->assertConnection(__METHOD__); + + if( !in_array($this->authMethod, $this->serverAuthMethods) ) { + throw new WrongStateException("Auth method '{$this->authMethod}' is not supported by server; use: ".implode(', ', $this->serverAuthMethods)); + } + // Start authentication + switch ($this->authMethod) { + case 'PLAIN': { + try { + $this->execCommand('AUTH '.$this->authMethod.' '.base64_encode("\0".$username."\0".$password), 235); + } catch(SmtpTransportException $e) { + throw new SmtpTransportException('Authentication not accepted from server', $e->getCode(), $e); + } + } break; + case 'LOGIN': { + try { + $this->execCommand('AUTH '.$this->authMethod, 334); + $this->execCommand(base64_encode($username), 334); + $this->execCommand(base64_encode($password), 235); + } catch(SmtpTransportException $e) { + throw new SmtpTransportException('Authentication not accepted from server', $e->getCode(), $e); + } + } break; + case 'CRAM-MD5': { + try { + $reply = $this->execCommand('AUTH '.$this->authMethod, 334); + // Get the challenge + $challenge = base64_decode($reply['payload']); + // Build the response + $response = base64_encode( $username . ' ' . $this->hmac($challenge, $password) ); + $this->execCommand($response, 235); + } catch(SmtpTransportException $e) { + throw new SmtpTransportException('Authentication not accepted from server', $e->getCode(), $e); + } + } break; + } + + $this->debugOut('SMTP: Authentication passed', self::DEBUG_MINIMUM); + + return $this; + } + + /** + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more Recipient + * commands may be called followed by a Data command. + * + * Implements rfc 821: MAIL FROM: + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE SUCCESS: 552, 451, 452 + * SMTP CODE SUCCESS: 500, 501, 421 + * @param string $from + * @throws SmtpTransportException + * @throws WrongArgumentException + * @throws WrongStateException + * @return SmtpTransport + */ + public function startMail($from) { + $this->assertConnection(__METHOD__); + $this->assertValidEmail($from); + + $command = 'MAIL FROM:<' . $from . '>'; + if( $this->isUseVerp() ) { + $command .= ' XVERP'; + } + try { + $this->execCommand($command, 250); + } catch(SmtpTransportException $e) { + throw new SmtpTransportException('MAIL not accepted from server', $e->getCode(), $e); + } + + $this->debugOut('SMTP: mail from :: ' . $from, self::DEBUG_MEDIUM); + + return $this; + } + + /** + * Sends the command RCPT to the SMTP server with the TO: argument of $to. + * Returns true if the recipient was accepted false if it was rejected. + * + * Implements from rfc 821: RCPT TO: + * + * SMTP CODE SUCCESS: 250, 251 + * SMTP CODE FAILURE: 550, 551, 552, 553, 450, 451, 452 + * SMTP CODE ERROR : 500, 501, 503, 421 + * @param string $to + * @throws SmtpTransportException + * @throws WrongArgumentException + * @throws WrongStateException + * @return SmtpTransport + */ + public function setRecipient($to) { + $this->assertConnection(__METHOD__); + $this->assertValidEmail($to); + + try { + $this->execCommand('RCPT TO:<' . $to . '>', 250); + } catch(SmtpTransportException $e) { + throw new SmtpTransportException('RCPT not accepted from server', $e->getCode(), $e); + } + + $this->lastRecipient = $to; + $this->debugOut('SMTP: rctp to :: ' . $to, self::DEBUG_MEDIUM); + + return $this; + } + + /** + * Issues a data command and sends the msg_data to the server + * finializing the mail transaction. $msg_data is the message + * that is to be send with the headers. Each header needs to be + * on a single line followed by a with the message headers + * and the message body being separated by and additional . + * + * Implements rfc 821: DATA + * + * SMTP CODE INTERMEDIATE: 354 + * [data] + * . + * SMTP CODE SUCCESS: 250 + * SMTP CODE FAILURE: 552, 554, 451, 452 + * SMTP CODE FAILURE: 451, 554 + * SMTP CODE ERROR : 500, 501, 503, 421 + * @param string $data + * @throws SmtpTransportException + * @throws MailNotSentException + * @throws WrongArgumentException + * @return SmtpTransport + */ + public function setContent($data) { + $this->assertConnection(__METHOD__); + + try { + $this->execCommand('DATA', 354); + } catch(SmtpTransportException $e) { + throw new SmtpTransportException('DATA not accepted from server', $e->getCode(), $e); + } + + /* the server is ready to accept data! + * according to rfc 821 we should not send more than 1000 + * including the CRLF + * characters on a single line so we will break the data up + * into lines by \r and/or \n then if needed we will break + * each of those into smaller lines to fit within the limit. + * in addition we will be looking for lines that start with + * a period '.' and append and additional period '.' to that + * line. NOTE: this does not count towards limit. + */ + + // normalize the line breaks so we know the explode works + $data = str_replace(array("\r\n", "\r"), "\n", $data); + $lines = explode("\n", $data); + + + $field = substr($lines[0], 0, strpos($lines[0], ':')); + $in_headers = false; + if(!empty($field) && !strstr($field, ' ')) { + $in_headers = true; + } + + $max_line_length = 998; // used below; set here for ease in change + + while(list(, $line) = @each($lines)) { + $lines_out = null; + if($line == '' && $in_headers) { + $in_headers = false; + } + // ok we need to break this line up into several smaller lines + while(strlen($line) > $max_line_length) { + $pos = strrpos(substr($line, 0, $max_line_length), ' '); + + // Patch to fix DOS attack + if(!$pos) { + $pos = $max_line_length - 1; + $lines_out[] = substr($line, 0, $pos); + $line = substr($line, $pos); + } else { + $lines_out[] = substr($line, 0, $pos); + $line = substr($line, $pos + 1); + } + + /* if processing headers add a LWSP-char to the front of new line + * rfc 822 on long msg headers + */ + if($in_headers) { + $line = "\t" . $line; + } + } + $lines_out[] = $line; + + // send the lines to the server + while(list(, $line_out) = @each($lines_out)) { + if(strlen($line_out) > 0) + { + if(substr($line_out, 0, 1) == '.') { + $line_out = '.' . $line_out; + } + } + $this->execCommand($line_out, 250); + } + } + + // message data has been sent + try { + $this->execCommand('.', 251); + } catch(SmtpTransportException $e) { + throw new MailNotSentException($e->getMessage(), $e->getCode()); + } + + $this->debugOut('SMTP: mail sent to :: '.$this->lastRecipient, self::DEBUG_MINIMUM); + + return $this; + } + + /** + * Sends the RSET command to abort and transaction that is + * currently in progress. Returns true if successful false + * otherwise. + * + * Implements rfc 821: RSET + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE ERROR : 500, 501, 504, 421 + * @throws SmtpTransportException + * @throws WrongStateException + * @return SmtpTransport + */ + public function reset() { + $this->assertConnection(__METHOD__); + + // clear last recipient + $this->lastRecipient = null; + + // send the quit command to the server + try { +// $this->execCommand('RSET', 250); + } catch(SmtpTransportException $e) { + throw new SmtpTransportException('RSET not accepted from server', $e->getCode(), $e); + } + + return $this; + } + + /** + * Sends the quit command to the server and then closes the socket + * if there is no error or the $close_on_error argument is true. + * + * Implements from rfc 821: QUIT + * + * SMTP CODE SUCCESS: 221 + * SMTP CODE ERROR : 500 + * @throws SmtpTransportException + * @throws WrongStateException + * @return SmtpTransport + */ + public function quit() { + $this->assertConnection(__METHOD__); + + // send the quit command to the server + try { + $this->execCommand('QUIT', 221); + } catch(SmtpTransportException $e) { + throw new SmtpTransportException('QUIT not accepted from server', $e->getCode(), $e); + } + + $this->debugOut('SMTP: quited', self::DEBUG_MINIMUM); + + return $this; + } + + /** + * Closes the socket and cleans up the state of the class. + * It is not considered good to use this function without + * first trying to use QUIT. + * @return SmtpTransport + */ + public function close() { + $this->error = null; // so there is no confusion + $this->helo_rply = null; + if(!empty($this->connection)) { + // close the connection and cleanup + fclose($this->connection); + $this->connection = null; + } + + $this->debugOut('SMTP: connection closed', self::DEBUG_MINIMUM); + + return $this; + } + + /** + * Returns true if connected to a server otherwise false + * @return bool + */ + public function isConnected() { + if(!empty($this->connection)) { + $sock_status = stream_get_meta_data($this->connection); + if($sock_status['eof']) { + // the socket is valid but we are not connected + $this->debugOut('SMTP->NOTICE: EOF caught while checking if connected', self::DEBUG_TOTAL); + $this->close(); + return false; + } + return true; // everything looks good + } + return false; + } + + /** + * Assert established connection + * @param string $method + * @throws WrongStateException + */ + protected function assertConnection($method) { + if(!$this->isConnected()) { + throw new WrongStateException("Called $method() without being connected"); + } + } + + /** + * Assert valid email address + * @param string $email + * @throws WrongArgumentException + */ + protected function assertValidEmail($email) { + $form = + Form::create() + ->add( + Primitive::string('email') + ->setAllowedPattern(PrimitiveString::MAIL_PATTERN) + ->setMin(6) + ->setMax(1024) + ->required() + ) + ->import( + array('email'=>$email) + ); + if( $form->getErrors() ) { + throw new WrongArgumentException("Provided string \"{$email}\" is not valid email"); + } + } + + //===== internal functions + + protected function execCommand($command, $expectedCode) { + if( !is_null($command) ) { + $this->rawPut($command . $this->crlf); + $this->debugOut('', self::DEBUG_MAXIMUM); + $this->debugOut("Send command {$command}", self::DEBUG_MAXIMUM); + } + $reply = trim($this->rawGet()); + + $code = intval( trim(substr($reply, 0, 4)) ); + $payload = substr($reply, 4); + + $this->debugOut("Got answer with code {$code} and payload: {$payload}", self::DEBUG_MAXIMUM); + if( $code!=$expectedCode ) { + throw new SmtpTransportException("Wrong answer code got {$code} but {$expectedCode} expected; last command: {$command}"); + } + + return array( + 'code' => $code, + 'payload' => $payload, + ); + } + + /** + * Read in as many lines as possible + * either before eof or socket timeout occurs on the operation. + * With SMTP we can tell if we have more lines to read if the + * 4th character is '-' symbol. If it is a space then we don't + * need to read anything else. + * @throws NetworkException + * @return string + */ + protected function rawGet() { + $data = ''; + $endtime = 0; + /* If for some reason the fp is bad, don't inf loop */ + if (!is_resource($this->connection)) { + return $data; + } + stream_set_timeout($this->connection, $this->streamTimeout); + if ($this->operationTimeLimit > 0) { + $endtime = time() + $this->operationTimeLimit; + } + while(is_resource($this->connection) && !feof($this->connection)) { + $str = @fgets($this->connection, 515); + $data .= $str; + // if 4th character is a space, we are done reading, break the loop + if(substr($str, 3, 1) == ' ') { break; } + // Timed-out? Log and break + $info = stream_get_meta_data($this->connection); + if ($info['timed_out']) { + $message = "SMTP->rawGet(): timed-out ({$this->streamTimeout} seconds)"; + $this->debugOut($message, self::DEBUG_MINIMUM); + throw new NetworkException($message); + } + // Now check if reads took too long + if ($endtime) { + if (time() > $endtime) { + $message = "SMTP->rawGet(): timelimit reached ({$this->operationTimeLimit} seconds)"; + $this->debugOut($message, self::DEBUG_MINIMUM); + throw new NetworkException($message); + } + } + } + $this->debugOut("SMTP get data: \"$data\"", self::DEBUG_TOTAL); + return $data; + } + + /** + * Sends data to the server + * Returns number of bytes sent to the server or FALSE on error + * @param string $data + * @return int + */ + protected function rawPut($data) { + $this->debugOut("SMTP put data: $data", self::DEBUG_TOTAL); + return fwrite($this->connection, $data); + } + + /** + * Works like hash_hmac('md5', $data, $key) in case that function is not available + * @access protected + * @param string $data + * @param string $key + * @return string + */ + protected function hmac($data, $key) { + if (function_exists('hash_hmac')) { + return hash_hmac('md5', $data, $key); + } + + // The following borrowed from http://php.net/manual/en/function.mhash.php#27225 + + // RFC 2104 HMAC implementation for php. + // Creates an md5 HMAC. + // Eliminates the need to install mhash to compute a HMAC + // Hacked by Lance Rushing + + $b = 64; // byte length for md5 + if (strlen($key) > $b) { + $key = pack('H*', md5($key)); + } + $key = str_pad($key, $b, chr(0x00)); + $ipad = str_pad('', $b, chr(0x36)); + $opad = str_pad('', $b, chr(0x5c)); + $k_ipad = $key ^ $ipad ; + $k_opad = $key ^ $opad; + + return md5($k_opad . pack('H*', md5($k_ipad . $data))); + } + + /** + * @param string $message + * @param int $minLevel + * @throws WrongStateException + */ + protected function debugOut($message, $minLevel = 100) { + if( $this->debugLevel < $minLevel ) { + return; + } + if( !is_callable($this->debugWriter) ) { + throw new WrongStateException('Debug writer is not callable!'); + } + call_user_func($this->debugWriter, $message); + } + +} \ No newline at end of file diff --git a/main/Net/Mail/SmtpTransportException.class.php b/main/Net/Mail/SmtpTransportException.class.php new file mode 100644 index 0000000000..0b548d4ce9 --- /dev/null +++ b/main/Net/Mail/SmtpTransportException.class.php @@ -0,0 +1,15 @@ +ctpp = new ctpp(); + } + + /** + * @param string $templateName + * @return CtppView + */ + public function setTemplateName($templateName) { + $this->templateName = $templateName; + return $this; + } + + /** + * @param array $templatePaths + * @return CtppView + */ + public function setTemplatePaths(array $templatePaths) { + $this->templatePaths = $templatePaths; + return $this; + } + + /** + * @param string $templatePath + * @return CtppView + */ + public function addTemplatePath($templatePath) { + $this->templatePaths[] = $templatePath; + return $this; + } + + /** + * @param string $templateExt + * @return CtppView + */ + public function setTemplateExt($templateExt) { + $this->templateExt = $templateExt; + return $this; + } + + /** + * @param int $steps_limit + * @return CtppView + */ + public function setStepsLimit($steps_limit) { + if( is_int($steps_limit) ) { + $this->steps_limit = $steps_limit; + } else { + throw new WrongArgumentException('Steps limit nust be integer!'); + } + + return $this; + } + + /** + * @param Model + * @return mixed + */ + public function render(/* Model */ $model = null) { + $this->makeByteCode(); + $this->setData( $model ); + return $this->ctpp->output( $this->bytecode ); + } + + /** + * @param Model + * @return mixed + */ + public function toString(/* Model */ $model = null) { + $this->makeByteCode(); + $this->setData( $model ); + $result = $this->ctpp->output_string( $this->bytecode ); + if( $result === false ) { + throw new CtppException( $this->ctpp->get_last_error() ); + } + return $result; + } + + private function makeByteCode() { + if( empty($this->templatePaths) ) { + throw new WrongStateException('Template paths are not defined!'); + } + if( empty($this->templateName) ) { + throw new WrongStateException('Template name is not defined!'); + } + // TODO: прикрутить кэширование байткода + // making bytecode + $this->ctpp->include_dirs( $this->templatePaths ); + $this->bytecode = $this->ctpp->parse_template( $this->templateName.'.'.$this->templateExt ); + } + + private function setData(Model $model = null) { + $this->ctpp->emit_params( $model->getList() ); + } + +} diff --git a/main/UI/View/DebugPhpView.class.php b/main/UI/View/DebugPhpView.class.php old mode 100644 new mode 100755 diff --git a/main/UI/View/EmptyGifView.class.php b/main/UI/View/EmptyGifView.class.php old mode 100644 new mode 100755 diff --git a/main/UI/View/EmptyView.class.php b/main/UI/View/EmptyView.class.php old mode 100644 new mode 100755 diff --git a/main/UI/View/ExtendedPhpView.class.php b/main/UI/View/ExtendedPhpView.class.php new file mode 100755 index 0000000000..15ca6d7b8a --- /dev/null +++ b/main/UI/View/ExtendedPhpView.class.php @@ -0,0 +1,79 @@ + + * @date 2012.03.20 + */ +class ExtendedPhpView extends SimplePhpView { + + protected + /** + * @var PartViewer + */ + $partViewer = null, + /** + * вьюшка, которую выпаолним до основной + * @var string + */ + $preView = null, + /** + * вьюшка, которую выполним после основной + * @var string + */ + $postView = null; + + /** + * @param string $preView + * @return ExtendedPhpView + */ + public function setPreView($preView) { + $this->preView = $preView; + return $this; + } + + /** + * @param string $postView + * @return ExtendedPhpView + */ + public function setPostView($postView) { + $this->postView = $postView; + return $this; + } + + /** + * @param Model $model + * @return ExtendedPhpView + */ + public function render(/* Model */ $model = null) + { + Assert::isTrue($model === null || $model instanceof Model); + + if ($model) + extract($model->getList()); + + $this->partViewer = $partViewer = new PartViewer($this->partViewResolver, $model); + + $this->preRender(); + + include $this->templatePath; + + $this->postRender(); + + return $this; + } + + protected function preRender() { + if( !is_null($this->preView) ) { + $this->partViewer->view($this->preView); + } + return $this; + } + + protected function postRender() { + if( !is_null($this->postView) ) { + $this->partViewer->view($this->postView); + } + return $this; + } + +} diff --git a/main/UI/View/ExtendedView.class.php b/main/UI/View/ExtendedView.class.php new file mode 100644 index 0000000000..69bb3bf30b --- /dev/null +++ b/main/UI/View/ExtendedView.class.php @@ -0,0 +1,62 @@ + + * @date 10.07.13 + */ + +class ExtendedView implements View { + /** @var View[] */ + protected $before = array(); + /** @var View[] */ + protected $after = array(); + /** @var View */ + protected $body = null; + + protected function __construct(View $body) { + $this->body = $body; + } + + public static function create(View $body) { + return new static($body); + } + + public function addHeader(View $view) { + $this->before []= $view; + return $this; + } + + public function addFooter(View $view) { + $this->after []= $view; + return $this; + } + + /** + * @param $model null or Model + * @return self + **/ + public function render($model = null) { + // begin rendering + ob_start(); + + // render headers + foreach ($this->before as $pre) { + $pre->render($model); + } + + // render body + $this->body->render($model); + + // render footers + foreach ($this->after as $post) { + $post->render($model); + } + + // done rendering + ob_end_flush(); + + return $this; + } + + +} \ No newline at end of file diff --git a/main/UI/View/HttpErrorView.class.php b/main/UI/View/HttpErrorView.class.php old mode 100644 new mode 100755 diff --git a/main/UI/View/JsonView.class.php b/main/UI/View/JsonView.class.php index 3df47ca817..c2255188e6 100644 --- a/main/UI/View/JsonView.class.php +++ b/main/UI/View/JsonView.class.php @@ -15,6 +15,7 @@ final class JsonView implements View, Stringable { protected $options = 0; + protected $callback = null; /** * @return JsonView @@ -147,13 +148,34 @@ public function setUnescapedSlashes($flag = false) return $this; } + + /** + * создание callback + * + * @param $callback + * @return JsonView + */ + public function setCallBack($callback) + { + $this->callback = $callback; + + return $this; + } /** * @return JsonView **/ public function render(/* Model */ $model = null) { - echo $this->toString($model); + if (!headers_sent()) { + header('Content-Type: ' . ($this->callback ? 'text/javascript' : 'application/json')); + } + if (is_null($this->callback)) { + echo $this->toString($model); + } + else { + echo $this->callback.'('.$this->toString($model).')'; + } return $this; } diff --git a/main/UI/View/MultiPrefixPhpViewResolver.class.php b/main/UI/View/MultiPrefixPhpViewResolver.class.php old mode 100644 new mode 100755 diff --git a/main/UI/View/PartViewer.class.php b/main/UI/View/PartViewer.class.php old mode 100644 new mode 100755 diff --git a/main/UI/View/PhpViewResolver.class.php b/main/UI/View/PhpViewResolver.class.php old mode 100644 new mode 100755 diff --git a/main/UI/View/RedirectToView.class.php b/main/UI/View/RedirectToView.class.php old mode 100644 new mode 100755 diff --git a/main/UI/View/RedirectView.class.php b/main/UI/View/RedirectView.class.php old mode 100644 new mode 100755 diff --git a/main/UI/View/SimplePhpView.class.php b/main/UI/View/SimplePhpView.class.php old mode 100644 new mode 100755 diff --git a/main/UI/View/View.class.php b/main/UI/View/View.class.php old mode 100644 new mode 100755 diff --git a/main/UI/View/ViewResolver.class.php b/main/UI/View/ViewResolver.class.php old mode 100644 new mode 100755 diff --git a/main/UI/Widget/BaseWidget.class.php b/main/UI/Widget/BaseWidget.class.php new file mode 100755 index 0000000000..aaf982bada --- /dev/null +++ b/main/UI/Widget/BaseWidget.class.php @@ -0,0 +1,162 @@ + + * @date 2012.03.11 + */ +abstract class BaseWidget implements IWidget +{ + protected $name = null; + protected $templatePath = null; + protected $templateName = null; + protected $list = null; + + protected static $templatePathPrefixes = array(); + protected static $viewer = null; + + /** + * @var PartViewer + */ + + public function __construct($name = null) + { + $this->name = $name; + } + + protected static function getViewer() { + if (!self::$viewer) { + $viewResolver = MultiPrefixPhpViewResolver::create() + ->setViewClassName('SimplePhpView'); + + foreach (self::$templatePathPrefixes as $prefix) { + $viewResolver->addPrefix($prefix); + } + self::$viewer = new PartViewer($viewResolver, Model::create()); + } + return self::$viewer; + } + + /** + * @param PartViewer $viewer + * @return $this + */ + public static function setViewer(PartViewer $viewer) + { + self::$viewer = $viewer; + } + + + public static function addTemplatePrefix($prefix) { + self::$templatePathPrefixes []= $prefix; + self::$viewer = null; + } + + /** + * @param string $key + * @param mixed $value + * @return BaseWidget + */ + public function set($key, $value) + { + if( ( mb_substr($key, 0, 1) != '_' ) ) + $key = '_'.$key; + + $this->list[$key] = $value; + + + return $this; + } + + /** + * @return BaseWidget + */ + public function setTemplateName($value) + { + $this->templateName = $value; + + return $this; + } + + /** + * @return string + */ + public function getTemplateName() + { + return $this->templateName; + } + + /** + * @return Model + */ + protected function makeModel() + { + $model = $this->getViewer()->getModel(); + $model->set('_name', $this->name); + + if( + is_array( $this->list ) && + count( $this->list ) + ){ + foreach ( $this->list as $key => $value ) { + $model->set($key, $value); + } + } + + return $model; + } + + /** (non-PHPdoc) + * @see core/Base/Stringable::toString() + * @throws Exception + * @return string + */ + public function toString() + { + try { + $this->makeModel(); + + ob_start(); + + $this->getViewer()->view( + $this->templatePath. DIRECTORY_SEPARATOR. $this->templateName + ); + + $source = ob_get_contents(); + + ob_end_clean(); + } + catch (Exception $e) { + // FIXME + error_log(__METHOD__ . ': '.$e->__toString() ); + throw $e; + } + + return (string) $source; + } + + /** (non-PHPdoc) + * @see main/Flow/View::render() + * @param Model|null $model + * @return void + */ + public function render($model = null) + { + if( $model ) + $this->getViewer()-> + getModel()-> + merge($model); + + echo $this->toString(); + + return /*void*/; + } + + /** + * @return string + */ + public function __toString() + { + return $this->toString(); + } +} + diff --git a/main/UI/Widget/DataGrid.class.php b/main/UI/Widget/DataGrid.class.php new file mode 100755 index 0000000000..a52cad5d97 --- /dev/null +++ b/main/UI/Widget/DataGrid.class.php @@ -0,0 +1,1045 @@ + + * @date 2012.03.19 + */ +class DataGrid extends BaseWidget +{ + /** @var string имя таблицы */ + protected $name = null; + + /** @var DataGrid для вложенных таблиц */ + protected $parent = null; + + /** @var int предел вложенности */ + protected static $maxNesting = 1; + + /** @var array список дочерних объектов */ + protected $objects = array(); + + + /** @var array список 'fieldID' => 'Field name' */ + protected $fields = array(); + + /** @var array список локализованных полей */ + protected $localizedFields = null; + + /** @var array список полей, по которым можно сортировать. + * Фактически, исключает поля, добавленные через addColumn */ + protected $sortingFields = array(); + + /** @var array список 'fieldID' => callback() */ + protected $renderers = array(); + + /** @var array массив строк таблицы */ + protected $rows = array(); + + /** @var int ИД строки с "итого" */ + protected $totalId = null; + + + /** @var array аттрибуты заголовков таблицы */ + protected $fieldHtmlOptions = array(); + + /** @var array аттрибуты
*/ + protected $formHtmlOptions = array(); + + /** @var bool показывать начальный тег */ + protected $showHeadTag = true; + + /** @var bool показывать заголовки или нет */ + protected $showHeader = true; + + /** @var bool показывать кнопки сортировки */ + protected $showSorting = true; + + /** @var bool делать редактируемые поля */ + protected $isEditor = false; + + /** @var Form для вывода ошибок в заполнении полей */ + protected $form = null; + + /** @var string текстовый вывод булевого типа */ + public $trueName = null; + + /** @var string текстовый вывод булевого типа */ + public $falseName = null; + + /** @var array аттрибуты строки таблицы */ + public $rowAttrs = array(); + + /** + * Создает таблицу вида сводки (заголовок слева) + * @static + * @return DataGrid + */ + public static function details() { + $self = static::create(); + $self->templateName = 'DataGrid_details'; + $self->showSorting = false; + return $self; + } + + /** + * Создает обычную таблицу + * @static + * @return DataGrid + */ + public static function table() { + $self = static::create(); + $self->templateName = 'DataGrid_table'; + $self->showSorting = true; + return $self; + } + + /** + * DataGrid::details() в виде редактируемой формы. + * Следует добавлять только 1 объект. + * @static + * @return DataGrid + */ + public static function editor($subfield = null) { + $self = static::details(); + $self->isEditor = true; + return $self; + } + + /** + * @static + * @return DataGrid + */ + private static function create() { + $self = new static(); + $self->trueName = __('Да'); + $self->falseName = __('Нет'); + return $self; + } + + /** + * Добавляем имя таблицы (для get параметров) + * @param $name + * @return $this + */ + public function setName($name) { + $this->name = $name; + return $this; + } + + /** + * Вызывает addRow для каждой из переданных строк + * @param array $data + * @return DataGrid + * @throws WrongArgumentException + */ + public function addRows(array $data) { + foreach ($data as $key=>$row) { + $this->addRow($row, $key); + } + return $this; + } + + /** + * Добавляет строку в таблицу, определяет тип и имена ее полей + * @param $data + * @param null $key + * @throws WrongArgumentException + * @return DataGrid + */ + public function addRow($data, $key=null) { + $rowId = count($this->rows); // id следующей строки + if( isset($key) && $key=='total' ) { + $this->totalId = $rowId; + } + + // если это объект, то смотрим его поля в протипе + // и через геттеры получаем все параметры, а если + // это массив, то берем его как есть. + + if ($data instanceof Prototyped) { + if ( + $data instanceof TranslatableObject + && is_null($this->localizedFields) + ) { + $dataProto = $data->proto(); + if ($dataProto instanceof ProtoTranslatableObject) { + $this->localizedFields = $dataProto->getTranslatablePropertyNames(); + } + } + + /** @var $data Prototyped */ + $this->objects[$rowId] = $data; + $fieldIds = array(); +// $this->rows[$rowId] = null; + $row = array(); + /** @var $property LightMetaProperty */ + foreach ($data->proto()->getPropertyList() as $property) { +// try { +// $value = $this->getPropertyValue($rowId, $property); +// } catch (BadMethodCallException $e) { +// continue; +// } catch (ObjectNotFoundException $e) { +// $value = null; +// } + $fieldIds[] = $property->getName(); + $row[$property->getName()] = null; +// $value = null; + } + } else if (is_array($data)) { + $fieldIds = array_keys($data); + $this->objects[$rowId] = $data; + $row = $data; + } else if ($data instanceof NamedObject) { + $this->objects[$rowId] = $data; + $row = array('id' => $data->getId(), 'name' => $data->getName()); + $fieldIds = array_keys($row); + } else { + throw new WrongArgumentException('$data should be either array or prototyped object'); + } + // сохраним в список сортируемых полей + foreach ($fieldIds as $fieldId) { + if (!in_array($fieldId, $this->sortingFields)) { + $this->sortingFields[] = $fieldId; + } + } + + // построим массив полей в виде 'имяПоля' => 'Имя поля' + // ключ - имя параметра, значение - имя для отображения + $fields = array(); + foreach($fieldIds as $fieldId) { + $fieldName = static::beautifyFieldName($fieldId); + $fields[$fieldId] = $fieldName; + } + // сливаем с существующим списком, чтобы ничего не потерять, + // если например отработали не все геттеры, и поле пропущено + $this->fields = array_merge($this->fields, $fields); + + // записываем данные + foreach($row as $fieldId => $value) { + $property = ($data instanceof Prototyped) ? $data->proto()->getPropertyByName($fieldId) : null; + $this->setField($rowId, $fieldId, $value, $property); + } + + return $this; + } + + /** + * @param $rowId + * @param $property + * @return mixed + */ + protected function getPropertyValue($rowId, $property) { + $getter = $property->getGetter(); + $value = $this->objects[$rowId]->$getter(); + return $value; + } + + /** + * Выставляет значение конкретного поля конкретной строки + * @param $rowId + * @param $fieldId + * @param $value + * @param $property LightMetaProperty если null, определится + * @return DataGrid + */ + private function setField($rowId, $fieldId, $value, $property = null) { + $this->rows[$rowId][$fieldId] = $value; + if (!isset($this->renderers[$fieldId])) { + if ($this->isEditor) { + if ($property == null) { + /** @var $object Prototyped */ + $object = $this->objects[$rowId]; + if ($object instanceof Prototyped) { + $property = $object->proto()->getPropertyByName($fieldId); + } + + } + + if ($property instanceof LightMetaProperty) { + $this->renderers[$fieldId] = $this->getEditRenderer($fieldId, $property); + } + } else { + if($property instanceof LightMetaProperty) { + $this->renderers[$fieldId] = $this->getLazyViewRenderer($fieldId, $property); + } elseif($value !== null) { + $this->renderers[$fieldId] = $this->getViewRenderer($value); + } + } + } + + return $this; + } + + + /** + * Дополнительная колонка + * @param string $fieldName + * @param Closure $renderer callback + * @param string|null $fieldId + * @return DataGrid + */ + public function addColumn($fieldName, $renderer, $fieldId = null) { + // если это поле не для данных (иконки действий, например) + // можно сгенерить рандомное имя поля, т.к. оно не важно + if ($fieldId === null) { + $fieldId = md5($fieldName); + } else { + // Если поле указано явно, добавим сразу для него сортировку + $this->sortingFields[] = $fieldId; + } + + $this->fields[$fieldId] = $fieldName; + $this->setRenderer($fieldId, $renderer); + return $this; + } + + /** + * @param string $fieldId + * @param LightMetaProperty $property + * @return closure + * @throws ClassNotFoundException + */ + protected function getEditRenderer($fieldId, LightMetaProperty $property) { + switch($property->getType()) { + case 'IpCidrRange': + case 'IpRange': + case 'IpAddress': + case 'integer': + case 'float': + case 'string': + case 'hstore': + return function ($value) use ($fieldId, $property) { + if ($value instanceof Stringable) $value = $value->toString(); + $value = htmlentities($value, ENT_COMPAT, 'UTF-8', false); + if (in_array($property->getType(), array('string', 'hstore')) && !$property->getMax()) { + return ''; + } else { + if ($property->getType() == 'string') { + $length = $property->getMax(); + $styleWidth = '100%'; + } else if ($property->getType() == 'hstore') { + $length = $property->getMax(); + $styleWidth = '100%'; + } else { + $length = 16; + $styleWidth = '80px'; + } + + return ''; + } + + }; + + case 'timestamp': + case 'date': + return function ($value) use ($fieldId, $property) { + if ($value instanceof Date) + $val = $value->toDate('-'); + else $val = ''; + + return ''; + }; + + case 'boolean': + return function ($value) use ($fieldId) { + return ''; + }; + + case 'enum': + case 'enumeration': + return function ($value) use ($fieldId, $property) { + $class = $property->getClassName(); + if (!class_exists($class, true)) { + throw new ClassNotFoundException; + } + $list = is_subclass_of($class, 'Enum') ? $class::getList() : $class::makeObjectList(); + $html = ''; + return $html; + }; + + case 'integerIdentifier': + return function ($value) use ($property) { + if ($value instanceof Identifiable) { + $value = $value->getId(); + } + return $property->getClassName() . ' ID: ' . $value; + }; + + case 'identifierList': + return function ($value) use ($property) { + //if (is_subclass_of($property->getClassName(), 'Enumeration')) { + // return 'enum'; + //} else { + return $property->getClassName(); + //} + }; + + default: + return function ($value) use ($property) { + // DEBUG + $props[] = 'name: ' . $property->getName(); + $props[] = 'className: ' . $property->getClassName(); + $props[] = 'type: ' . $property->getType(); + $props[] = 'min: ' . $property->getMin(); + $props[] = 'max: ' . $property->getMax(); + $props[] = 'relation: ' . $property->getRelationId(); + $props[] = 'fetch: ' . $property->getFetchStrategyId(); + //$props[] = 'value: ' . $value; + return implode(', ', $props); + }; + } + } + + /** + * @param string $fieldId + * @param LightMetaProperty $property + * @return closure + * @throws ClassNotFoundException + */ + protected function getLazyViewRenderer($fieldId, LightMetaProperty $property) { + // переменные для замыканий, т.к. они не биндятся к this + $self = $this; + $trueName = $this->trueName; + $falseName = $this->falseName; + + switch($property->getType()) { + + // OneToOne + case 'integerIdentifier': + case 'scalarIdentifier': { + if( $property->isIdentifier() ) { + return function ($value) use ($property) { + return $value; + }; + } elseif( is_subclass_of($property->getClassName(), 'Prototyped') ) { + return function($value, $object) use ($self) { + if ($value == null) { return '(NULL)'; } + if ($self->hasParent($object)) { + return get_class($value) . ' ID:' . $value->getId(); + } else { + try { + return + '' . get_class($value) . '
' . + DataGrid::details()->setParent($self)->addRow($value)->ToString(); + } catch (Exception $e) { + return $e->getMessage(); + } + } + }; + } else { + return function ($value) { + return 'Object is not prototyped!'; + }; + } + } + + // OneToMany & ManyToMany + case 'identifierList': { + return function(UnifiedContainer $value, $object) use ($self) { + if ($self->hasParent($object)) { + return get_class($value) . ' count:' . $value->fetch()->getCount(); + } else { + try { + return DataGrid::table()->setParent($self)->addRows($value->fetch()->getList())->ToString(); + } catch (Exception $e) { + return $e->getMessage(); + } + } + }; + } + + case 'boolean': { + return function ($value) use ($trueName, $falseName) { + return $value ? $trueName : $falseName; + }; + } + + case 'set': { + return function($value) use ($self) { + try { + return DataGrid::table()->setParent($self)->addRows($value)->ToString(); + } catch (Exception $e) { + return $e->getMessage(); + } + }; + } + + case 'IpCidrRange': + case 'IpRange': + case 'IpAddress': { + return function ($value) { + return $value ? $value->toString() : ''; + }; + } + + case 'integer': { + return function ($value) { + return $value; + }; + } + + case 'float': { + return function ($value) { + return number_format($value, 2, ',', ''); + }; + } + + case 'string': { + return function ($value) { + return nl2br($value); + }; + } + + case 'timestamp': + case 'date': { + return function ($value) { + if( $value instanceof Timestamp ) { + return $value->toFormatString('d-m-Y H:i:s'); + } elseif( $value instanceof Date ) { + return $value->toFormatString('d-m-Y'); + } else { + return ''; + } + }; + } + + case 'enum': + case 'enumeration': { + return function ($value) { + if( $value instanceof Enumeration || $value instanceof Enum ) { + return $value->getName(); + } else { + return ''; + } + }; + } + + case 'hstore': { + return function ($value) { + if( $value instanceof Hstore || $value instanceof Enum ) { + return $value->getName(); + } else if (is_string($value)) { + return $value; + } else { + return ''; + } + }; + } + + default: + return function ($value) use ($property) { + // DEBUG + $props[] = 'name: ' . $property->getName(); + $props[] = 'className: ' . $property->getClassName(); + $props[] = 'type: ' . $property->getType(); + $props[] = 'min: ' . $property->getMin(); + $props[] = 'max: ' . $property->getMax(); + $props[] = 'relation: ' . $property->getRelationId(); + $props[] = 'fetch: ' . $property->getFetchStrategyId(); + //$props[] = 'value: ' . $value; + return implode(', ', $props); + }; + } + } + + /** + * Находит подходящий рендерер в соответствии с типом значения + * @param $value + * @return Closure + */ + protected function getViewRenderer($value) { + // переменные для замыканий, т.к. они не биндятся к this + $self = $this; + $trueName = $this->trueName; + $falseName = $this->falseName; + + // для прототипированного объекта можно построить + // вложенную табличку. Важно запомнить родителя, + // чтобы избежать бесконечной рекурсии + if ($value instanceof Prototyped) + return function($value, $object) use ($self) { + if ($self->hasParent($object)) { + return get_class($value) . ' ID:' . $value->getId(); + } else { + try { + return + '' . get_class($value) . '
' . + DataGrid::details()->setParent($self)->addRow($value)->ToString(); + } catch (Exception $e) { + return $e->getMessage(); + } + } + }; + + + // Это в основном для тех случаев, когда у объекта есть + // OneToMany свойство. UnifiedContainer позволяет получить + // список дочерних объектов + if ($value instanceof UnifiedContainer) + return function(UnifiedContainer $value, $object) use ($self) { + if ($self->hasParent($object)) { + return get_class($value) . ' count:' . $value->fetch()->getCount(); + } else { + try { + return DataGrid::table()->setParent($self)->addRows($value->fetch()->getList())->ToString(); + } catch (Exception $e) { + return $e->getMessage(); + } + } + }; + + + // Булевы выведем для удобства словами "Да" или "Нет" + if (is_bool($value)) + return function ($value) use ($trueName, $falseName) { + return $value ? $trueName : $falseName; + }; + + + // Встроенная табличка + if (is_array($value)) + return function($value) use ($self) { + try { + return DataGrid::table()->setParent($self)->addRows($value)->ToString(); + } catch (Exception $e) { + return $e->getMessage(); + } + }; + + + // Заглушка для всех прочих объектов + if (is_object($value)) + return function ($value) { + if (is_null($value)) { + return ''; + } + if ($value instanceof Stringable) + return $value->toString(); + if (method_exists($value, '__toString')) + return (string)$value; + return 'object('.get_class($value).')'; + }; + + if (is_string($value)) + return function ($value) { + return nl2br($value); + }; + + // прочие случаи + return function($value) { return $value; }; + } + + /** + * 'somePropertyName' => 'Some property name' + * @param $camelCaseString + * @return string + */ + public static function beautifyFieldName($camelCaseString) { + return ucfirst( + preg_replace_callback( + '/([a-z])([A-Z])/', + function($x) { + return $x[1] . ' ' . strtolower($x[2]); + }, + $camelCaseString + ) + ); + } + + /** + * @param DataGrid $parent + * @return DataGrid + */ + public function setParent(DataGrid $parent) { + $this->parent = $parent; + return $this; + } + + /** + * Рекурсивная проверка + * @param $object + * @return bool + */ + public function hasParent($object) { + $dataGrid = $this; + $nesting = 0; + while ($dataGrid = $dataGrid->parent) { + if ($nesting++ > static::$maxNesting) + return true; + foreach ($dataGrid->objects as $o) { + if ($o == $object) { + return true; + } + if ($o instanceof Identifiable && $object instanceof Identifiable + && get_class($o) == get_class($object) + && $o->getId() == $object->getId()) { + return true; + } + } + } + return false; + } + + /** + * @param $number + * @return DataGrid + */ + public function setMaxNesting($number) { + static::$maxNesting = $number; + return $this; + } + + /** + * @return int + */ + public static function getMaxNesting() { + return static::$maxNesting; + } + + public function getFields() { + return $this->fields; + } + + /** + * Задает массив столбцов таблицы + * @param array $fields array('fieldID' => 'Field name', ...) + * @return DataGrid + */ + public function setFields(array $fields) { + $this->fields = $fields; + + /* При добавлении объекта, из него выгребаются все свойства, + * но только его собственные, а зависимые объекты не подгружаются + * Допустим, мы хотим добавить employee.user.name для отображении + * имени менеджера, когда выводим инфу по рекламодателю. Тогда + * следующий код сделает следующую цепочку вызовов: + * $advertiser->getEmployee()->getUser()->getName() + * и заполнит соответствующие поля таблицы значениями. + * */ + + foreach ($this->fields as $fieldId => $fieldName) { + if (strpos($fieldId, '.') !== false) { + $path = explode('.', $fieldId); + foreach ($this->rows as $rowId => $row) { + $object = $this->objects[$rowId]; + $property = null; + $failed = false; + foreach ($path as $propertyName) { + + if ($object instanceof Prototyped) { + try { + $property = $object->proto()->getPropertyByName($propertyName); + $getter = $property->getGetter(); + $object = $object->$getter(); + continue; + } catch (MissingElementException $e) { + // none + } + } + + if (is_object($object)) { + try { + // support non-prototyped getters + $getter = 'get' . ucfirst($propertyName); + Assert::methodExists($object, $getter); + $object = $object->$getter(); + continue; + } catch (WrongArgumentException $e) { + // none + } + } + + if (is_array($object) && isset($object[$propertyName])) { + $object = $object[$propertyName]; + continue; + } + + $failed = true; + break; + } + + if (!$failed) { + $this->setField($rowId, $fieldId, $object, $property); + $this->sortingFields[] = $fieldId; + } + } + } + } + + return $this; + } + + /** + * Исключает столбцы из таблицы + * можно использовать маски, напр: unsetFields('webmaster.*'); + * @param $_ string, string, ... OR array + * @return DataGrid + */ + public function unsetFields($_) { + if (is_array($_)) $fields = $_; + else $fields = func_get_args(); + foreach($fields as $fieldId) { + if (substr($fieldId, -1) == '*') { + $prefix = substr($fieldId, 0, strlen($fieldId) - 1); + foreach ($this->fields as $fieldId => $fieldName) { + if (substr($fieldId, 0, strlen($prefix)) == $prefix) { + unset($this->fields[$fieldId]); + } + } + } else { + unset($this->fields[$fieldId]); + } + } + return $this; + } + + /** + * @param $fieldId string + * @param $callback Closure function($value, $rowObject) {} + * @return DataGrid + */ + public function setRenderer($fieldId, $callback) { + $this->renderers[$fieldId] = $callback; + return $this; + } + + /** + * Показать начальный тег + * @return DataGrid + */ + public function showHeadTag() { + $this->showHeadTag = true; + return $this; + } + + /** + * Скрыть начальный тег + * @return DataGrid + */ + public function hideHeadTag() { + $this->showHeadTag = false; + return $this; + } + + /** + * Показать названия колонок + * @return DataGrid + */ + public function showHeader() { + $this->showHeader = true; + return $this; + } + + /** + * Скрыть названия колонок + * @return DataGrid + */ + public function hideHeader() { + $this->showHeader = false; + return $this; + } + + /** + * @param array $fields + */ + public function setSortingFields(array $fields) { + $this->sortingFields = $fields; + return $this; + } + + /** + * Исключает столбцы из списка столбцов для сортировки + * @param $_ string, string, ... OR array + * @return DataGrid + */ + public function unsetSortingFields($_) { + if (is_array($_)) $fields = $_; + else $fields = func_get_args(); + foreach($fields as $fieldId) { + foreach($this->sortingFields as $k=>$v) { + if ($v == $fieldId) { + unset($this->sortingFields[$k]); + } + } + } + return $this; + } + + /** + * @return DataGrid + */ + public function showSorting() { + $this->showSorting = true; + return $this; + } + + /** + * @return DataGrid + */ + public function hideSorting() { + $this->showSorting = false; + return $this; + } + + /** + * @param string $fieldId + * @param array $htmlOptions + * @return DataGrid + */ + public function setHeaderOptions($fieldId, $htmlOptions) { + $this->fieldHtmlOptions[$fieldId] = $htmlOptions; + return $this; + } + + /** + * @param array $htmlOptions + * @return DataGrid + */ + public function setFormOptions($htmlOptions) { + $this->formHtmlOptions = $htmlOptions; + return $this; + } + + /** + * @param $formErrors + * @return DataGrid + */ + public function setForm(Form $form) { + $this->form = $form; + return $this; + } + + /** + * + * @param array $rowAttrs + * @return DataGrid + */ + public function setRowAttrs($rowAttrs) { + $this->rowAttrs = $rowAttrs; + return $this; + } + + /** + * @return Model + */ + protected function makeModel() { + $data = array(); + + if (empty($this->rowAttrs['class'])) { + $this->rowAttrs['class'] = ''; + } + + // рендерим данные + foreach ($this->rows as $rowId => $row) { + $object = isset($this->objects[$rowId]) ? $this->objects[$rowId] : $this->rows[$rowId]; + foreach ($this->fields as $fieldId => $fieldName) { + $fieldPath = explode('.', $fieldId); + $field = $object; + foreach ($fieldPath as $fieldPathPart) { + if ($field instanceof Prototyped && PrototypeUtils::hasProperty($field, $fieldPathPart)) { + $field = PrototypeUtils::getValue($field, $fieldPathPart); + } elseif ($field instanceof NamedObject && $fieldPathPart == 'name') { + $field = $field->getName(); + } elseif ($field instanceof Identifiable && $fieldPathPart == 'id') { + $field = $field->getId(); + } elseif (is_array($field) && isset($field[$fieldPathPart])) { + $field = $field[$fieldPathPart]; + } else { + $field = null; + break; + } + } + + if ($this->form instanceof Form && $this->form->exists($fieldId)) { + if ($this->form->hasError($fieldId)) { + $field = $this->form->get($fieldId)->getRawValue(); + } + else if ($this->form->get($fieldId)->isImported()) { + $field = $this->form->get($fieldId)->getValue(); + } + } + + // если есть рендерер, прогоним значение через него + if (isset($this->renderers[$fieldId])) { + $callback = $this->renderers[$fieldId]; + if ($this->renderers[$fieldId] instanceof Closure) { + $field = $callback($field, $object); + } else { + $field = call_user_func($callback, $field, $object); + } + } + $data[$rowId][$fieldId] = $field; + } + + $attrs = array(); + + foreach ($this->rowAttrs as $key => $value) { + if ($value instanceof Closure) { + $value = $value($object); + } +// if ('class' == $key) { +// $value = ($rowId % 2 ? 'odd ' : 'even ') . $value; +// } + array_push($attrs, $key . '="' . $value . '"'); + } + $data[$rowId]['attrs'] = implode(' ', $attrs); + } + + // отрендерим аттрибуты html + $htmlOptions = array(); + foreach ($this->fields as $fieldId => $fieldName) { + if (isset($this->fieldHtmlOptions[$fieldId])) { + $htmlOptions[$fieldId] = Html::attributes($this->fieldHtmlOptions[$fieldId]); + } else { + $htmlOptions[$fieldId] = ''; + } + } + + // отрендерим аттрибуты формы + $selfUrl = null; + try { + $controller = Application::me()->getRunningActionController(); + $selfUrl = $controller->getSelfUrl()->__toString(); + } catch (UnexpectedValueException $e) { + $selfUrl = $_SERVER['REQUEST_URI']; + $this->hideSorting(); + } + $formOptions = Html::attributes(array_merge( + array('action' => $selfUrl, 'method' => 'POST'), + $this->formHtmlOptions + )); + + $model = parent::makeModel() + ->set('name', $this->name) + ->set('fields', $this->fields) + ->set('localizedFields', $this->localizedFields ? : array()) + ->set('data', $data) + ->set('totalId', $this->totalId) + ->set('htmlOptions', $htmlOptions) + ->set('formOptions', $formOptions) + ->set('showHeadTag', $this->showHeadTag) + ->set('showHeader', $this->showHeader) + ->set('showSorting', $this->showSorting) + ->set('sortingFields', $this->sortingFields) + ->set('isEditor', $this->isEditor) + ->set('form', $this->form); + ; + + return $model; + } +} diff --git a/main/UI/Widget/IWidget.class.php b/main/UI/Widget/IWidget.class.php new file mode 100755 index 0000000000..dd5df64f11 --- /dev/null +++ b/main/UI/Widget/IWidget.class.php @@ -0,0 +1,15 @@ +list, + "that's not an array :-/" + ); + + if (!$this->fetched) { + throw new WrongStateException( + 'do not want to save non-fetched collection' + ); + } + + $idList = array(); + if( current($this->list) instanceof NoSqlObject ) { + /** @var $object NoSqlObject */ + foreach($this->list as &$object) { + $idList[] = $object->getId(); + } + } else { + $idList = $this->list; + } + $this->dao->dropByIds($idList); + + $this->clean(); + + return $this; + } + + public function save() { + Assert::isArray( + $this->list, + "that's not an array :-/" + ); + + if (!$this->fetched) { + throw new WrongStateException( + 'do not want to save non-fetched collection' + ); + } + + /** @var $object NoSqlObject */ + foreach( $this->list as &$object ) { + if( $object instanceof NoSqlObject ) { + if( $object->getId() ) { + $object->dao->save( $object ); + } else { + $object->dao->add( $object ); + } + } + } + + return $this; + } + + /** + * @abstract + * @return array + */ + protected function fetchList() { + if( $this->lazy ) { + $this->list = $this->dao->getIdListByField( $this->getParentIdField(), $this->parent->getId(), $this->worker->getCriteria() ); + } else { + $this->list = $this->dao->getListByField( $this->getParentIdField(), $this->parent->getId(), $this->worker->getCriteria() ); + } + + return $this; + } + + public function getCount() + { + if (!$this->isFetched() && $this->parent->getId()) { + return $this->dao->getCountByField( $this->getParentIdField(), $this->parent->getId(), $this->worker->getCriteria() ); + } + + return count($this->list); + } + + } diff --git a/main/UnifiedContainer/UnifiedContainer.class.php b/main/UnifiedContainer/UnifiedContainer.class.php old mode 100644 new mode 100755 index ee7d33a5bd..d366ea8acb --- a/main/UnifiedContainer/UnifiedContainer.class.php +++ b/main/UnifiedContainer/UnifiedContainer.class.php @@ -57,7 +57,7 @@ protected function getParentTableIdField() * * @ingroup Containers **/ - abstract class UnifiedContainer + abstract class UnifiedContainer implements DialectString { protected $worker = null; protected $parent = null; @@ -107,6 +107,10 @@ public function __wakeup() $this->worker = new $this->workerClass($this); } + public function __clone() { + $this->worker = clone $this->worker; + } + public function getParentObject() { return $this->parent; @@ -367,7 +371,7 @@ public function dropList() protected function fetchList() { $query = $this->worker->makeFetchQuery(); - + if ($this->lazy) { $list = $this->dao->getCustomRowList($query); @@ -422,5 +426,82 @@ private function syncClones() return $this; } + + /** + * @param Dialect $dialect + * @return string + */ + public function toDialectString(Dialect $dialect) { + $query = $this->worker->makeFetchQuery(); + return $query->toDialectString($dialect); + } + + /** + * @param Identifiable|int $item + * @return static + */ + public function addItem($item) { + if ($this->isLazy()) { + Assert::isScalar($item); + } else { + Assert::isInstance($item, $this->getDao()->getObjectName()); + } + + if ($this->getParentObject()->getId() && !$this->isFetched()) { + $this->fetch(); + } + + $this->mergeList([ $item ]); + + return $this; + } + + /** + * @param Identifiable|int $item + * @return static + */ + public function removeItem($item) { + $list = $this->getList(); + + if ($this->isLazy()) { + Assert::isScalar($item); + $filter = function ($i) use ($item) { + return $i != $item; + }; + } else { + Assert::isInstance($item, $this->getDao()->getObjectName()); + $filter = function (Identifiable $i) use ($item) { + return $i->getId() != $item->getId(); + }; + } + + $this->setList( array_filter($list, $filter) ); + + return $this; + } + + /** + * @param Identifiable|int $item + * @return bool + */ + public function hasItem($item) { + $list = $this->getList(); + + if ($this->isLazy()) { + Assert::isScalar($item); + $filter = function ($i) use ($item) { + return $i == $item; + }; + } else { + Assert::isInstance($item, $this->getDao()->getObjectName()); + $filter = function (Identifiable $i) use ($item) { + return $i->getId() == $item->getId(); + }; + } + + $matches = array_filter($list, $filter); + + return count($matches) > 0; + } } ?> \ No newline at end of file diff --git a/main/UnifiedContainer/UnifiedContainerWorker.class.php b/main/UnifiedContainer/UnifiedContainerWorker.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/AMQP.class.php b/main/Utils/AMQP/AMQP.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/AMQPBaseChannel.class.php b/main/Utils/AMQP/AMQPBaseChannel.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/AMQPBaseConfig.class.php b/main/Utils/AMQP/AMQPBaseConfig.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/AMQPBaseMessage.class.php b/main/Utils/AMQP/AMQPBaseMessage.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/AMQPBitmaskResolver.class.php b/main/Utils/AMQP/AMQPBitmaskResolver.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/AMQPChannelInterface.class.php b/main/Utils/AMQP/AMQPChannelInterface.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/AMQPConsumer.class.php b/main/Utils/AMQP/AMQPConsumer.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/AMQPCredentials.class.php b/main/Utils/AMQP/AMQPCredentials.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/AMQPDefaultConsumer.class.php b/main/Utils/AMQP/AMQPDefaultConsumer.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/AMQPExchangeConfig.class.php b/main/Utils/AMQP/AMQPExchangeConfig.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/AMQPExchangeType.class.php b/main/Utils/AMQP/AMQPExchangeType.class.php old mode 100644 new mode 100755 index 14410b4f6c..f701db5932 --- a/main/Utils/AMQP/AMQPExchangeType.class.php +++ b/main/Utils/AMQP/AMQPExchangeType.class.php @@ -23,6 +23,10 @@ final class AMQPExchangeType extends Enumeration self::HEADER => "header" ); + public static function create($id) { + return new self($id); + } + public function getDefault() { return self::DIRECT; diff --git a/main/Utils/AMQP/AMQPIncomingMessage.class.php b/main/Utils/AMQP/AMQPIncomingMessage.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/AMQPOutgoingMessage.class.php b/main/Utils/AMQP/AMQPOutgoingMessage.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/AMQPPool.class.php b/main/Utils/AMQP/AMQPPool.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/AMQPQueueConfig.class.php b/main/Utils/AMQP/AMQPQueueConfig.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/AMQPQueueConsumer.class.php b/main/Utils/AMQP/AMQPQueueConsumer.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/Exceptions/AMQPServerConnectionException.class.php b/main/Utils/AMQP/Exceptions/AMQPServerConnectionException.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/Exceptions/AMQPServerException.class.php b/main/Utils/AMQP/Exceptions/AMQPServerException.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/Pecl/AMQPPecl.class.php b/main/Utils/AMQP/Pecl/AMQPPecl.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/Pecl/AMQPPeclBaseBitmask.class.php b/main/Utils/AMQP/Pecl/AMQPPeclBaseBitmask.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/Pecl/AMQPPeclChannel.class.php b/main/Utils/AMQP/Pecl/AMQPPeclChannel.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/Pecl/AMQPPeclExchangeBitmask.class.php b/main/Utils/AMQP/Pecl/AMQPPeclExchangeBitmask.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/Pecl/AMQPPeclOutgoingMessageBitmask.class.php b/main/Utils/AMQP/Pecl/AMQPPeclOutgoingMessageBitmask.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/AMQP/Pecl/AMQPPeclQueueBitmask.class.php b/main/Utils/AMQP/Pecl/AMQPPeclQueueBitmask.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Archivers/ArchiverException.class.php b/main/Utils/Archivers/ArchiverException.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Archivers/FileArchive.class.php b/main/Utils/Archivers/FileArchive.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Archivers/InfoZipArchive.class.php b/main/Utils/Archivers/InfoZipArchive.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Archivers/TarArchive.class.php b/main/Utils/Archivers/TarArchive.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/ArrayUtils.class.php b/main/Utils/ArrayUtils.class.php old mode 100644 new mode 100755 index 98a55839d5..2a92d1a0af --- a/main/Utils/ArrayUtils.class.php +++ b/main/Utils/ArrayUtils.class.php @@ -259,5 +259,13 @@ public static function mergeSortedLists( return $newList; } + + public static function hardMerge(array $one, array $two) { + foreach ($two as $key=>$value) { + $one[$key] = $value; + } + return $one; + } + } ?> \ No newline at end of file diff --git a/main/Utils/ClassUtils.class.php b/main/Utils/ClassUtils.class.php old mode 100644 new mode 100755 index 8f995eb8a0..e77ea91e81 --- a/main/Utils/ClassUtils.class.php +++ b/main/Utils/ClassUtils.class.php @@ -14,7 +14,7 @@ **/ final class ClassUtils extends StaticFactory { - const CLASS_NAME_PATTERN = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'; + const CLASS_NAME_PATTERN = '(\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+'; /* void */ public static function copyProperties($source, $destination) { @@ -223,6 +223,7 @@ public static function checkStaticMethod($methodSignature) if ( !class_exists($className) && !interface_exists($className) + && !trait_exists($className) ) { include $file; } diff --git a/main/Utils/CommandLine/ArgumentParser.class.php b/main/Utils/CommandLine/ArgumentParser.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/CommandLine/FormToArgumentsConverter.class.php b/main/Utils/CommandLine/FormToArgumentsConverter.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/CommonDoctypeDeclaration.class.php b/main/Utils/CommonDoctypeDeclaration.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/ContentTypeHeader.class.php b/main/Utils/ContentTypeHeader.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/CustomizableDaoSynchronizer.class.php b/main/Utils/CustomizableDaoSynchronizer.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/CyrillicPairs.class.php b/main/Utils/CyrillicPairs.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/DaoIterator.class.php b/main/Utils/DaoIterator.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/DaoMoveHelper.class.php b/main/Utils/DaoMoveHelper.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/DaoSynchronizer.class.php b/main/Utils/DaoSynchronizer.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/DaoUtils.class.php b/main/Utils/DaoUtils.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/DateUtils.class.php b/main/Utils/DateUtils.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/DebugUtils.class.php b/main/Utils/DebugUtils.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/DoctypeDeclaration.class.php b/main/Utils/DoctypeDeclaration.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/FileUtils.class.php b/main/Utils/FileUtils.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/FullTextUtils.class.php b/main/Utils/FullTextUtils.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/IO/BufferedInputStream.class.php b/main/Utils/IO/BufferedInputStream.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/IO/BufferedReader.class.php b/main/Utils/IO/BufferedReader.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/IO/FileInputStream.class.php b/main/Utils/IO/FileInputStream.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/IO/FileOutputStream.class.php b/main/Utils/IO/FileOutputStream.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/IO/FileReader.class.php b/main/Utils/IO/FileReader.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/IO/InputStream.class.php b/main/Utils/IO/InputStream.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/IO/OutputStream.class.php b/main/Utils/IO/OutputStream.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/IO/Reader.class.php b/main/Utils/IO/Reader.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/IO/Socket.class.php b/main/Utils/IO/Socket.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/IO/SocketInputStream.class.php b/main/Utils/IO/SocketInputStream.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/IO/SocketOutputStream.class.php b/main/Utils/IO/SocketOutputStream.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/IO/StringInputStream.class.php b/main/Utils/IO/StringInputStream.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/IO/StringReader.class.php b/main/Utils/IO/StringReader.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Logging/BaseLogger.class.php b/main/Utils/Logging/BaseLogger.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Logging/LogLevel.class.php b/main/Utils/Logging/LogLevel.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Logging/LogRecord.class.php b/main/Utils/Logging/LogRecord.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Logging/StreamLogger.class.php b/main/Utils/Logging/StreamLogger.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/LogicUtils.class.php b/main/Utils/LogicUtils.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Mobile/MobileRequestDetector.class.php b/main/Utils/Mobile/MobileRequestDetector.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Mobile/MobileUtils.class.php b/main/Utils/Mobile/MobileUtils.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Profiling.class.php b/main/Utils/Profiling.class.php new file mode 100644 index 0000000000..0cadc0c2a1 --- /dev/null +++ b/main/Utils/Profiling.class.php @@ -0,0 +1,178 @@ + + * @date 09.12.13 + */ + +class Profiling { + protected static $history = array(); + protected static $historyEnabled = false; + + protected $timeStart = null; + protected $timeEnd = null; + protected $tags = array(); + protected $info = null; + protected $trace = null; + + public static function create($tags = array(), $info = null) { + $self = new static(); + if (!is_array($tags)) { + $tags = array($tags); + } + $self->tags = $tags; + $self->info = $info; + $backtrace = debug_backtrace(); + array_shift($backtrace); + $self->trace = $backtrace; + return $self; + } + + /** + * @param $tag + * @return self[] + */ + public static function getHistory($tag) { + if (isset(self::$history[$tag])) { + return self::$history[$tag]; + } + return array(); + } + + public static function isHistoryEnabled() { + return self::$historyEnabled; + } + + public static function setHistoryEnabled($bool) { + self::$historyEnabled = ($bool === true); + } + + public static function getTotalTime($tag) { + $time = 0; + foreach (self::getHistory($tag) as $profiling) { + $time += $profiling->getTimeMs(); + } + return $time; + } + + public function begin() { + $this->timeStart = microtime(true); + return $this; + } + + public function end($saveToHistory = null) { + $this->timeEnd = microtime(true); + + if (is_null($saveToHistory)) { + $saveToHistory = self::isHistoryEnabled(); + } + + if ($saveToHistory) { + foreach ($this->getTags() as $tag) { + if (!isset(self::$history[$tag])) { + self::$history[$tag] = array(); + } + self::$history[$tag] []= $this; + } + } + + return $this; + } + + public function getTime() { + if ($this->timeStart == null) { + return -INF; + } else if ($this->timeEnd == null) { + return +INF; + } + return $this->timeEnd - $this->timeStart; + } + + public function getTimeMs(){ + return round($this->getTime() * 1000, 3); + } + + public function getTimeStart($format = 'Y-m-d H:i:s.u') { + $time = $this->timeStart; + if ($time === null) { + return $format ? 'never' : 0; + } + if( strpos($time,'.')===false ) { + $time .= '.0'; + } + if ($format) { + $time = DateTime::createFromFormat('U.u', $time)->format($format); + } + return $time; + } + + public function getTimeEnd($format = 'Y-m-d H:i:s.u') { + $time = $this->timeEnd; + if ($time === null) { + return $format ? 'never' : 0; + } + if( strpos($time,'.')===false ) { + $time .= '.0'; + } + if ($format) { + $time = DateTime::createFromFormat('U.u', $time)->format($format); + } + return $time; + } + + public function getTags() { + return $this->tags; + } + + public function getInfo(){ + return $this->info; + } + + public function getTrace() { + return $this->trace; + } + + public function getTraceAsString() { + return implode(PHP_EOL, array_map( + function ($bt) { + $string = ''; + if (isset($bt['file'])) { + $string .= basename($bt['file']) . '(' . $bt['line'] . '): '; + } + if (isset($bt['class'])) { + $string .= $bt['class'] . $bt['type']; + } + $string .= $bt['function']; + $string .= '('; + if (isset($bt['args'])) { + $string .= implode(', ', array_map(function ($arg) { + if (is_null($arg)) return 'NULL'; + else if (is_resource($arg)) return '{resource}'; + else if (is_array($arg)) return '{array(' . count($arg) .')}'; + else if (is_object($arg)) return '{' . get_class($arg) .'}'; + else if (is_scalar($arg)) { + if (is_string($arg) && strlen($arg) > 64) { + $arg = substr($arg, 0, 64) . '...'; + } + return var_export($arg, true); + } + else if (is_callable($arg)) return '{closure}'; + else return '{unknown}'; + }, $bt['args'])); + } + $string .= ')'; + return $string; + }, + $this->getTrace() + )); + } + + /** + * @param null $info + * @return $this + */ + public function setInfo($info) { + $this->info = $info; + return $this; + } +} \ No newline at end of file diff --git a/main/Utils/PrototypeUtils.class.php b/main/Utils/PrototypeUtils.class.php new file mode 100755 index 0000000000..63e9d70acc --- /dev/null +++ b/main/Utils/PrototypeUtils.class.php @@ -0,0 +1,313 @@ + + * @date 2012.03.23 + */ +class PrototypeUtils +{ + protected static $identifiers = array('identifier', 'integerIdentifier', 'scalarIdentifier', 'uuidIdentifier'); + + /** + * @static + * @param AbstractProtoClass $proto + * @param int $depth max depth + * @param string $prefix + * @param array $exclude + * @return array + */ + public static function getFullPropertyList(AbstractProtoClass $proto, $depth = 99, $prefix = '', $exclude = array()) { + $properties = $proto->getPropertyList(); + $values = array(); + foreach ($properties as $name=>$prop) { + $values[] = $prefix . $name; + if ($prop->isIdentifier()) { + $exclude[] = $prop->getClassName(); + } + $class = $prop->getClassName(); + if (strlen($class) && is_subclass_of($class, 'Prototyped')) { + if ( !in_array($class, $exclude) && $depth > 0) { + $values = array_merge($values, + self::getFullPropertyList($class::proto(), $depth-1, $prefix . $prop->getName() . '.', $exclude) + ); + } + } + } + return $values; + } + + /** + * @static + * @param AbstractProtoClass $proto + * @param array $fields + * @return Form + */ + public static function makeForm(AbstractProtoClass $proto, array $fields) { + $form = Form::create(); + foreach ($fields as $field) { + try { + $property = self::getProperty($proto, $field); + } catch (MissingElementException $e) { + continue; //skip + } + $prefix = strrev(strrchr(strrev($field), '.')); + $property->fillForm($form, $prefix); + $primitive = $form->get($field); + if ($primitive instanceof PrimitiveString) { + if ($property->getMax()) { + $primitive->setImportFilter(FilterFactory::makeText()); + } else { + $primitive->setImportFilter(FilterFactory::makeString()); + } + } + } + return $form; + } + + /** + * @static + * @param AbstractProtoClass $proto + * @param $path + * @return LightMetaProperty + */ + public static function getProperty(AbstractProtoClass $proto, $path) { + $path = explode('.', $path); + $subProto = $proto; + foreach ($path as $propertyName) { + /** @var $property LightMetaProperty */ + $property = $subProto->getPropertyByName($propertyName); + $class = $property->getClassName(); + if (strlen($class) && is_subclass_of($class, 'Prototyped')) + $subProto = $class::proto(); + else break; + } + return $property; + } + + public static function propertyExists(AbstractProtoClass $proto, $path) { + try { + return self::getProperty($proto, $path) instanceof LightMetaProperty; + } catch (MissingElementException $e) { + return false; + } + } + + /** + * @static + * @param Prototyped $object + * @param $path + * @return mixed + */ + public static function getValue(Prototyped $object, $path) { + $path = preg_split('/[\\:\\.]/', $path); + foreach ($path as $field) { + $getter = 'get' . ucfirst($field); + if (!method_exists($object, $getter)) { + throw new ObjectNotFoundException(implode('.', $path) . ' at ' . get_class($object) . '->'. $getter); + } + $object = $object->$getter(); + } + return $object; + } + + /** + * @static + * @param Prototyped $object + * @param $path + * @param $value + * @throws WrongArgumentException + */ + public static function setValue(Prototyped $object, $path, $value) { + $path = explode('.', $path); + $valueName = array_pop($path); + if ($path) + $object = self::getValue($object, implode('.', $path)); + + $setter = 'set' . ucfirst($valueName); + $dropper = 'drop' . ucfirst($valueName); + if (is_null($value) && method_exists($object, $dropper)) { + return $object->$dropper(); + } else { + return $object->$setter($value); + } + } + + public static function hasProperty(Prototyped $object, $path) { + try { + self::getValue($object, $path); + return true; + } catch (ObjectNotFoundException $e) { + return false; + } + } + + public static function getOwner(Prototyped $object, $path) { + $path = explode('.', $path); + array_pop($path); + if ($path) + $object = self::getValue($object, implode('.', $path)); + return $object; + } + + public static function getOwnerClass(Prototyped $object, $path) { + if (strpos($path, '.') === false) { + return get_class($object); + } + $parent = substr($path, 0, strrpos($path, '.')); + return self::getProperty($object->proto(), $parent)->getClassName(); + } + + /** + * @static + * @param Prototyped $object + * @param Form $form + * @return array modified objects to save + * @throws WrongArgumentException + */ + public static function fillObject(Prototyped $object, Form $form) { + $modifications = array(); + foreach ($form->getPrimitiveList() as $primitive) { + try { + $value = $primitive->getValue(); + $field = $primitive->getName(); + + if (!self::hasProperty($object, $field)) + continue; + + if (self::getValue($object, $field) != $value) { + self::setValue($object, $field, $value); + $owner = self::getOwner($object, $field); + $modifications[get_class($owner) . '#' . $owner->getId()] = $owner; + } + } catch (WrongArgumentException $e) { + throw $e; + } + } + + return ($modifications); + } + + /** + * @param Prototyped $object + * @return array + */ + public static function toArray(Prototyped $object) { + $entity = array(); + /** @var $property LightMetaProperty */ + foreach ($object->proto()->getPropertyList() as $property) { + // обрабатываем базовые типы + if( $property->isGenericType() ) { + $value = call_user_func(array($object, $property->getGetter())); + if( is_object( $value )&& $value instanceof Date ) { + $value = $value->toStamp(); + //$value = $value->toString(); + } + if( $property->getType() == 'integer' ) { + $entity[ $property->getColumnName() ] = ($value === null) ? null : (int)$value; + } elseif( $property->getType() == 'float' ) { + $entity[ $property->getColumnName() ] = ($value === null) ? null : (float)$value; + } elseif( $property->getType() == 'string' ) { + $value = (string)$value; + if ($property->getMax() > 0) { + $value = mb_substr($value, 0, $property->getMax()); + } + if ($value === false || $value === "") { + // если false или "", то null + $value = null; + } + $entity[ $property->getColumnName() ] = $value; + } elseif ($property->getType() == 'hstore') { + /** @var $value Hstore|null */ + $entity[ $property->getColumnName() ] = $value instanceof Hstore ? $value->getList() : null; + } else { + $entity[ $property->getColumnName() ] = $value; + } + } // обрабатываем перечисления + elseif (in_array($property->getType(), array('enumeration', 'enum', 'registry'))) { + /** @var Identifiable|null $value */ + $value = call_user_func(array($object, $property->getGetter())); + $entity[ $property->getColumnName() ] = $value instanceof Identifiable ? $value->getId() : null; + } // обрабатываем связи 1к1 + elseif($property->isInner()) { + $value = call_user_func(array($object, $property->getGetter())); + $entity[ $property->getColumnName() ] = $value instanceof Prototyped ? self::toArray($value) : null; + } + elseif( in_array($property->getType(), self::$identifiers) && $property->getRelationId()==1 ) { + $value = call_user_func(array($object, $property->getGetter().'Id')); + $entity[ $property->getColumnName() ] = is_numeric($value) ? (int)$value : $value; + } + } + return $entity; + } + + /** + * @param Prototyped $a first object + * @param Prototyped $b second object + * @param array $ignore properties to ignore + * @return bool + * @throws WrongArgumentException + */ + public static function same(Prototyped $a, Prototyped $b, $ignore = array('id')) { + // проверим что прото совпадают + if (get_class($a->proto()) != get_class($b->proto())) { + throw new WrongArgumentException('objects have different protos'); + } + + // берем первое прото + $proto = $a->proto(); + + // собираем список геттеров + $getters = array(); + foreach ($proto->getPropertyList() as $property) { + /** @var $property LightMetaProperty */ + + // исключаем указанные в параметре $ignore + if (in_array($property->getName(), $ignore)) { + continue; + } + + // обычные свойства + if ($property->getRelationId() == null) { + $getters []= $property->getGetter(); + } + + // свойства, ссылающиеся на объект - берем ID + if ($property->getRelationId() == MetaRelation::ONE_TO_ONE) { + $getters []= $property->getGetter() . 'Id'; + } + + // one-to-many, many-to-many не проверяем + } + + // сравнение + foreach ($getters as $getter) { + $valueA = $a->{$getter}(); + $valueB = $b->{$getter}(); + if ($valueA instanceof Date && $valueB instanceof Date) { + $valueA = $valueA->toStamp(); + $valueB = $valueB->toStamp(); + } + if ($valueA != $valueB) { + return false; + } + } + + return true; + } + + public static function copy(Prototyped $from, Prototyped $to, array $properties) { + foreach ($properties as $property) { + /** @var LightMetaProperty $property */ + if ($from->proto()->isPropertyExists($property->getName()) && $to->proto()->isPropertyExists($property->getName())) { + $value = self::getValue($from, $property->getName()); + self::setValue($to, $property->getName(), $value); + } + } + } + +} diff --git a/main/Utils/Retryer.php b/main/Utils/Retryer.php new file mode 100644 index 0000000000..76f0ded86e --- /dev/null +++ b/main/Utils/Retryer.php @@ -0,0 +1,104 @@ + + * @date 2014.05.18 + */ + +class Retryer { + + protected $code; + protected $retries = 3; + + /** @var int */ + protected $timeout = 1000000; //1 second (in microseconds) + protected $exceptions = array(); + + public static function create( $code ) { + return new static($code); + } + + public function __construct( $code ) { + $this->code = $code; + } + + /** + * @return array + */ + public function getExceptions() { + return $this->exceptions; + } + + /** + * @param int $retries + * @return static + */ + public function setRetries($retries) { + $this->retries = $retries; + return $this; + } + + /** + * @return int + */ + public function getRetries() { + return $this->retries; + } + + /** + * @param int|callable $timeout + * @return static + */ + public function setTimeout($timeout) { + $this->timeout = $timeout; + return $this; + } + + /** + * @return int|callable + */ + public function getTimeout() { + return $this->timeout; + } + + /** @return true|false */ + public function exec() { + if ( !$this->retries ) { + return false; + } + + $exceptions = array(); + $code = $this->code; + $result = null; + $success = false; + + for( $try = 1; $try <= $this->retries; $try++ ) { + $success = true; + try { + $result = $code(); + } + catch (Exception $e) { + $success = false; + $exceptions[] = $e; + } + + if ($success) { + break; + } + + if ( ($timeout = $this->timeout) && ($try < $this->retries) ) { + if (is_callable($timeout)) { + $timeout = $timeout($try); + } + usleep($timeout); + } + } + + $this->exceptions = $exceptions; + if ( !$success ) { + throw RetryerException::create('Failed after retries limit reached!', $this->exceptions); + } + + return $result; + } +} \ No newline at end of file diff --git a/main/Utils/RetryerException.class.php b/main/Utils/RetryerException.class.php new file mode 100644 index 0000000000..b15bed40c9 --- /dev/null +++ b/main/Utils/RetryerException.class.php @@ -0,0 +1,34 @@ + + * @date 2014.05.18 + */ + +class RetryerException extends Exception{ + + /** @var Exception[] */ + protected $exceptions = array(); + + /** @return array */ + public function getExceptions() { + return $this->exceptions; + } + + public static function create($message, $exceptions) { + $e = new static($message); + $e->exceptions = $exceptions; + return $e; + } + + public function getMessageReadable() { + $result = array(); + $try = 1; + foreach ($this->exceptions as $exception) { + $result[] = ($try++) . '. (' .get_class($exception) .') ' . $exception->getMessage() . ' @' . $exception->getFile() . ':' . $exception->getLine(); + } + + return implode("\n", $result); + } + +} \ No newline at end of file diff --git a/main/Utils/Routers/Router.class.php b/main/Utils/Routers/Router.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Routers/RouterBaseRule.class.php b/main/Utils/Routers/RouterBaseRule.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Routers/RouterChainRule.class.php b/main/Utils/Routers/RouterChainRule.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Routers/RouterException.class.php b/main/Utils/Routers/RouterException.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Routers/RouterHostnameRule.class.php b/main/Utils/Routers/RouterHostnameRule.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Routers/RouterRegexpRule.class.php b/main/Utils/Routers/RouterRegexpRule.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Routers/RouterRewrite.class.php b/main/Utils/Routers/RouterRewrite.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Routers/RouterRule.class.php b/main/Utils/Routers/RouterRule.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Routers/RouterStaticRule.class.php b/main/Utils/Routers/RouterStaticRule.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Routers/RouterTransparentRule.class.php b/main/Utils/Routers/RouterTransparentRule.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Routers/RouterUrlHelper.class.php b/main/Utils/Routers/RouterUrlHelper.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/RussianTextUtils.class.php b/main/Utils/RussianTextUtils.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/SortHelper.class.php b/main/Utils/SortHelper.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/Storage/Engines/StorageEngine.class.php b/main/Utils/Storage/Engines/StorageEngine.class.php new file mode 100644 index 0000000000..cb1919ed63 --- /dev/null +++ b/main/Utils/Storage/Engines/StorageEngine.class.php @@ -0,0 +1,351 @@ + + * @date 2013.01.1/23/13 + */ +class StorageEngine +{ + const DS = DS; + + protected $hasHttpLink = false; + protected $canReadRemote = true; + protected $ownNamingPolicy = true; + + protected $unAllowedName = '/[\/\:\*\?\"\<\>\|\\\]/iu'; + protected $unAllowedPath = '/[\.\/\:\*\?\"\<\>\|\\\]/iu'; + + protected $linkId = 'default'; + + protected static $tempFiles = array(); + + protected $canCopy = false; + + protected $canRename = false; + + protected $keepFiles = false; + + protected $trusted = true; + + protected $httpLink = null; + + protected $retries = 1; + protected $timeout = 1000000; // 1 sec + + protected $httpTimeout = 0; // seconds + + // folder sharding + protected $folderShardingDepth = 0; + protected $folderShardingNameBucketSize = 2; + protected $folderShardingEmptyKey = '0'; + + public static final function create(StorageEngineType $type = null, $linkId = null) { + if (!$type) { + return new self($linkId); + } + + $className = $type->toString(); + Assert::classExists($className); + + return new $className($linkId); + } + + public final function __construct($linkId) { + if ($linkId) { + $this->linkId = $linkId; + } + $config = StorageConfig::me()->getConfig( StorageEngineType::getByClass(get_class($this)), $this->linkId); + $this->parseConfig($config); + if ( isset($config['keepFiles']) && $config['keepFiles'] ) { + $this->keepFiles = true; + } + + if ( isset($config['retries']) && ($config['retries'] > 1) ) { + $this->retries = $config['retries']; + } + + if ( isset($config['timeout']) ) { + $this->timeout = $config['timeout']; + } + + if ( isset($config['httpTimeout']) ) { + $this->httpTimeout = $config['httpTimeout']; + } + } + + protected function parseConfig($data) { + return $this; + } + + public function hasHttpLink() { + return $this->hasHttpLink; + } + + public function hasOwnNamingPolicy() { + return $this->ownNamingPolicy; + } + + public function canReadRemote() { + return $this->canReadRemote; + } + + public function canCopy() { + return $this->canCopy; + } + + public function canRename() { + return $this->canRename; + } + + public function getHttpLink($file) { + if ($this->hasHttpLink()) { + return $this->httpLink.$this->generateSubPath($file).$file; + } + + throw new UnsupportedMethodException('Don`t know how to return http link'); + } + + protected function generateSubPath($fileName) { + if ( !$this->folderShardingDepth ) { + return ''; + } + + $fileName = preg_replace($this->unAllowedPath, '', $fileName); + $path = ''; + for( $i = 0; $i < $this->folderShardingDepth; $i++ ) { + + $delta = $this->folderShardingNameBucketSize - mb_strlen($fileName); + if ( $delta > 0 ) { + $key = str_pad($fileName, $this->folderShardingNameBucketSize, $this->folderShardingEmptyKey); + $fileName = ''; + } + else{ + $key = mb_substr($fileName, 0, $this->folderShardingNameBucketSize); + $fileName = mb_substr($fileName, $this->folderShardingNameBucketSize); + } + $path .= $key . self::DS; + } + + return $path; + } + + public function isTrusted() { + return $this->trusted; + } + + public function storeRemote($link, $desiredName=null) { + + if (!$desiredName) { + $desiredName = $this->generateName(''); + } + + $httpTimeout = ''; + try { + if ( $this->httpTimeout && strpos($link, 'http') === 0 ) { + $httpTimeout = floatval($this->httpTimeout); + $context = stream_context_create( array( + 'http' => array ( + 'timeout' => $httpTimeout, + 'user_agent' => 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0', + ) + )); + + $source = fopen($link, 'r', false, $context); + } + else { + $source = fopen($link, 'r'); + } + + if ( !$source ) { + throw new Exception('fopen failed' . $link); + } + } + catch(Exception $e) { + throw + new FileNotFoundException( + $e->getMessage() . ($httpTimeout ? ' (httpTimeout: ' . $httpTimeout . ' sec)' : ''), + $e->getCode(), + $e + ); + } + + $path = $this->getTmpFile($desiredName); + $destination = fopen($path, 'w'); + + stream_copy_to_stream($source, $destination); + + return $path; + } + + /** + * @param $file String + * @param $to String + * @return String + */ + public function copy($from, $to=null) { + throw new Exception('Don`t want to copy temporary files!'); + } + + /** + * @param $file String + * @param $to String + * @return String + */ + public function rename($from, $to) { + throw new Exception('Don`t want to rename temporary files!'); + } + + /** + * @param $file String + * @return String + */ + public function get($file) { + if (is_uploaded_file($file)) { + return $file; + } + + return $this->getTmpFile($file); + } + + /** + * @param $localFile String + * @param $desiredName String + * @return String + */ + public function store($localFile, $desiredName) { + throw new Exception('Can not store temporary file'); + } + + public final function remove($file) { + if ($this->keepFiles) { + return true; + } + else{ + return $this->unlink($file); + } + } + + protected function unlink($file) { + return true; + } + + public function exists($file) { + return false; + } + + protected function generateName($name) { + return uniqid().$name; + } + + protected function getTmpFile($file, $create = true) { + if (!is_scalar($file)) { + throw new InvalidArgumentException('Got filename of type: '.gettype($file)); + } + + if (!isset(self::$tempFiles[$file])) { + if ($create) { + self::$tempFiles[$file] = new TempFile(); + } + else{ + throw new InvalidArgumentException('Temporary file '.$file.' not found!'); + } + } + + return self::$tempFiles[$file]->getPath(); + } + + public function stat($file) { + $result = array('mime'=>'', 'size'=>0); + if ($this->hasHttpLink()) { + $result = array_merge($result, $this->httpStat($this->getHttpLink($file))); + } + + return $result; + } + + protected function httpExists($url) { + try { + $sendRequest = HttpRequest::create() + ->setMethod(HttpMethod::get()) + ->setUrl( + HttpUrl::create() + ->parse($url) + ); + + $status = CurlHttpClient::create() + ->setOption(CURLOPT_RETURNTRANSFER,true) + ->setOption(CURLOPT_NOBODY, true) + ->send($sendRequest) + ->getStatus() + ->getId(); + + if ( $status>=200 && $status<400 ) { + return true; + } + } + catch(Exception $e) {} + + return false; + } + + protected function httpStat($url) { + $result = array(); + try{ + $sendRequest = HttpRequest::create() + ->setMethod(HttpMethod::get()) + ->setUrl( + HttpUrl::create() + ->parse($url) + ); + + $res = CurlHttpClient::create() + ->setOption(CURLOPT_RETURNTRANSFER,true) + ->setOption(CURLOPT_NOBODY, true) + ->send($sendRequest); + + $result['mime'] = $res->getHeader('content-type'); + $result['size'] = $res->getHeader('content-length'); + } + catch(Exception $e) {} + + return $result; + } + + protected function tryToDo($action, $exceptionMessage) { + try { + if ( $this->retries > 1 ) { + $reTryer = Retryer::create( $action ) + ->setTimeout($this->timeout) + ->setRetries($this->retries); + + $result = $reTryer->exec(); + } else { + $result = $action(); + } + + } + catch (Exception $e) { + $message = ($e instanceof RetryerException) ? $e->getMessageReadable() : $e->getMessage(); + + throw new Exception(sprintf($exceptionMessage, $message)); + } + + return $result; + } + + protected function closeHandles() { + $handles = func_get_args(); + foreach($handles as $handle) { + if (!is_resource($handle)) { + continue; + } + try{ + fclose($handle); + } + catch (Exception $e) { } + } + + return $this; + } + +} diff --git a/main/Utils/Storage/Engines/StorageEngineFTP.class.php b/main/Utils/Storage/Engines/StorageEngineFTP.class.php new file mode 100644 index 0000000000..25fe26262b --- /dev/null +++ b/main/Utils/Storage/Engines/StorageEngineFTP.class.php @@ -0,0 +1,11 @@ + + * @date 2013.01.1/23/13 + */ +class StorageEngineFTP extends StorageEngineStreamable{ + + protected $canRename = true; + +} \ No newline at end of file diff --git a/main/Utils/Storage/Engines/StorageEngineHTTP.class.php b/main/Utils/Storage/Engines/StorageEngineHTTP.class.php new file mode 100644 index 0000000000..65af31885d --- /dev/null +++ b/main/Utils/Storage/Engines/StorageEngineHTTP.class.php @@ -0,0 +1,88 @@ + + * @date 2013.01.1/23/13 + */ +class StorageEngineHTTP extends StorageEngine{ + protected $hasHttpLink = false; + protected $canReadRemote = false; + protected $ownNamingPolicy = true; + + protected $trusted = true; + + protected $uploadUrl = null; + protected $httpLink = null; + protected $uploadOptions = array(); + + protected $uploadFieldName = 'file'; + protected $urlFieldName = 'url'; + + protected function parseConfig($data) { + + if (isset($data['uploadUrl'])) { + $this->uploadUrl = $data['uploadUrl']; + } + + if (isset($data['httpLink'])) { + $this->hasHttpLink = true; + $this->httpLink = $data['httpLink']; + } + + if (isset($data['uploadOptions'])&&is_array($data['uploadOptions'])) { + $this->uploadOptions = $data['uploadOptions']; + } + + if (isset($data['uploadFieldName'])) { + $this->uploadFieldName = $data['uploadFieldName']; + } + + if (isset($data['urlFieldName'])) { + $this->urlFieldName = $data['urlFieldName']; + } + } + + public function get($file) { + return parent::storeRemote($this->getHttpLink($file)); + } + + public function store($file, $desiredName) { + if (!$this->uploadUrl) { + throw new UnsupportedMethodException('Don`t know how to store file!'); + } + + $sendRequest = HttpRequest::create() + ->setMethod(HttpMethod::post()) + ->setUrl( + HttpUrl::create() + ->parse($this->uploadUrl) + ); + + $options = array_merge( + $this->uploadOptions, + array( + $this->uploadFieldName => '@'.$file + ) + ); + + $curl = CurlHttpClient::create() + ->setOption(CURLOPT_POSTFIELDS,$options); + + $upload = function() use ($curl, $sendRequest) { + $resp = $curl->send($sendRequest); + return $resp; + }; + + $resp = $this->tryToDo($upload, "Tried to upload file but something happened: %s"); + + return $resp->getBody(); + } + + public function exists($file) { + if ($this->hasHttpLink()) { + return $this->httpExists($this->getHttpLink($file)); + } + + return true; + } +} diff --git a/main/Utils/Storage/Engines/StorageEngineImageShack.class.php b/main/Utils/Storage/Engines/StorageEngineImageShack.class.php new file mode 100644 index 0000000000..05e1ebeec1 --- /dev/null +++ b/main/Utils/Storage/Engines/StorageEngineImageShack.class.php @@ -0,0 +1,22 @@ + + * @date 2013.01.1/23/13 + */ +class StorageEngineImageShack extends StorageEngineHTTP +{ + protected $hasHttpLink = true; + + public function store($localFile, $desiredName) { + + $xml = simplexml_load_string(parent::store($localFile, $desiredName)); + + if (isset($xml->error)) { + throw new Exception('Couldn`t save file to ImageShack: ' . $xml->error); + } + + return (string)$xml->links->image_link; + } + +} diff --git a/main/Utils/Storage/Engines/StorageEngineLocal.class.php b/main/Utils/Storage/Engines/StorageEngineLocal.class.php new file mode 100644 index 0000000000..c94a3100c1 --- /dev/null +++ b/main/Utils/Storage/Engines/StorageEngineLocal.class.php @@ -0,0 +1,27 @@ + + * @date 2013.04.18 + */ + +class StorageEngineLocal extends StorageEngineStreamable { + + protected function parseConfig ($data) { + if ( !isset($data['path']) ) { + throw new InvalidArgumentException('Path must be configured'); + } + + if ( preg_match('/(\:\/\/)/iu',$data['path']) ) { + throw new InvalidArgumentException('Path must not contain protocol: '.$data['path']); + } + + if ( !is_dir($data['path']) || !is_readable($data['path']) ) { + throw new InvalidArgumentException('Path must be readable directory: '.$data['path']); + } + + $data['dsn'] = $data['path']; + + return parent::parseConfig($data); + } +} \ No newline at end of file diff --git a/main/Utils/Storage/Engines/StorageEngineStreamable.class.php b/main/Utils/Storage/Engines/StorageEngineStreamable.class.php new file mode 100644 index 0000000000..b7e6a674cf --- /dev/null +++ b/main/Utils/Storage/Engines/StorageEngineStreamable.class.php @@ -0,0 +1,165 @@ + + * @date 2013.01.1/23/13 + */ +class StorageEngineStreamable extends StorageEngine +{ + protected $hasHttpLink = false; + protected $canReadRemote = false; + protected $ownNamingPolicy = false; + + protected $canCopy = false; + + protected $dsn = null; + protected $context = null; + + protected $httpLink = null; + + protected $resolveNameConflicts = true; + + protected function getPath($file, $createPath = false) { + if ( substr($this->dsn, strlen($this->dsn)-1, 1) != self::DS ) { + $this->dsn .= self::DS; + } + + $path = $this->dsn; + + if ($this->folderShardingDepth) { + + $path .= $this->generateSubPath($file); + + clearstatcache( true ); + + if (!is_dir($path)) { + if ($createPath) { + try{ + mkdir($path, 0777, true, $this->context); + } catch (Exception $e) { + if ($e->getMessage() !== 'mkdir(): File exists') { // на самый крайний случай + throw $e; + } + } + } + } + } + + return $path . $file; + } + + protected function parseConfig($data) { + if (!isset($data['dsn'])) + throw new Exception('No DSN configured for streamable storage: '.$this->linkId); + + $this->dsn = $data['dsn']; + + if (isset($data['httpLink'])) { + $this->hasHttpLink = true; + $this->httpLink = $data['httpLink']; + } + + $context = array(); + + if (isset($data['context'])&&is_array($data['context'])) { + $context = $data['context']; + } + + $this->context = stream_context_create($context); + + if (isset($data['folderSharding'])) { + $depth = 1; + if (isset($data['folderShardingDepth']) && is_integer($data['folderShardingDepth'])) { + $depth = $data['folderShardingDepth']; + } + $this->folderShardingDepth = $depth; + } + + if (isset($data['resolveNameConflicts'])) { + $this->resolveNameConflicts = (bool)$data['resolveNameConflicts']; + } + + return $this; + } + + public function get($file) { + $localFile = $this->getTmpFile($file); + $dst = fopen($localFile,'wb'); + $src = fopen($this->getPath($file),'rb', false, $this->context); + if (!stream_copy_to_stream($src, $dst)) { + throw new Exception('Couldn`t get file '.$file); + }; + + $this->closeHandles($dst, $src); + + return $localFile; + } + + public function rename ($from, $to) { + return rename($this->getPath($from), $this->getPath($to), $this->context); + } + + public function store ($localFile, $desiredName) { + if (!is_readable($localFile)||!is_file($localFile)) { + throw new WrongArgumentException('Wrong file: ' . $localFile); + } + + if (preg_match($this->unAllowedName,$desiredName)) { + throw new WrongArgumentException('Wrong desired name: '.$desiredName); + } + + $origDesiredName = $desiredName; + + $desiredNameFull = $this->getPath($desiredName, true); + + if ($this->exists($desiredName) && $this->resolveNameConflicts) { + $desiredName = $this->generateName( $desiredName ); + $desiredNameFull = $this->getPath( $desiredName, true ); + } + if ($this->exists($desiredName)) { + throw new Exception('File name conflict:'.$origDesiredName.'"'); + } + + $context = $this->context; + + $upload = function() use ($localFile, $desiredNameFull, $desiredName, $context) { + $src = fopen( $localFile, 'rb' ); + $dst = fopen( $desiredNameFull, 'wb', false, $context ); + Assert::isEqual( stream_copy_to_stream($src, $dst) , filesize($localFile), 'Bytes copied mismatch' ); + + $this->closeHandles($src, $dst); + + if (!$this->exists($desiredName)) { + throw new Exception('Could not find file after upload"' . $desiredName . '", linkId: ' . $this->linkId); + } + }; + + $this->tryToDo($upload, 'Couldn`t store file '.$origDesiredName.', reason: %s'); + + return $desiredName; + } + + public function exists ($file) { + try { + $handle = fopen($this->getPath($file),'rb', false, $this->context); + $result = $handle !== false; + + $this->closeHandles($handle); + + return $result; + } + catch (Exception $e) { + return false; + } + } + + protected function unlink($file) { + try { + return unlink($this->getPath($file),$this->context); + } + catch (Exception $e) { + return false; + } + } + +} diff --git a/main/Utils/Storage/Engines/StorageEngineURL.class.php b/main/Utils/Storage/Engines/StorageEngineURL.class.php new file mode 100644 index 0000000000..a6ac802494 --- /dev/null +++ b/main/Utils/Storage/Engines/StorageEngineURL.class.php @@ -0,0 +1,46 @@ + + * @date 2013.01.1/23/13 + */ +class StorageEngineURL extends StorageEngine{ + + protected $hasHttpLink = true; + protected $canReadRemote = true; + protected $ownNamingPolicy = true; + + protected $canCopy = false; + + protected $trusted = false; + + protected function checkUrl ($url) { + Url::create()->parse($url); + return $this; + } + + public function getHttpLink ($url) { + $this->checkUrl($url); + return $url; + } + + public function storeRemote ($link, $desiredName=null) { + $this->checkUrl($link); + return $link; + } + + public function get ($url) { + $this->checkUrl($url); + return parent::storeRemote($url); + } + + public function store ($local_file, $desiredName) { + throw new Exception('Can not store temporary file'); + } + + public function exists ($url) { + $this->checkUrl($url); + $this->httpExists($url); + } + +} \ No newline at end of file diff --git a/main/Utils/Storage/Engines/StorageEngineUploadedStatic.class.php b/main/Utils/Storage/Engines/StorageEngineUploadedStatic.class.php new file mode 100644 index 0000000000..d349b31876 --- /dev/null +++ b/main/Utils/Storage/Engines/StorageEngineUploadedStatic.class.php @@ -0,0 +1,40 @@ + + * @date 2013.01.1/23/13 + */ + +class StorageEngineUploadedStatic extends StorageEngineHTTP{ + protected $storagePath = null; + + protected function parseConfig ($data) { + parent::parseConfig($data); + + if (isset($data['storagePath'])) { + $this->storagePath = $data['storagePath']; + } + } + + public function getHttpLink ($file) { + if ( $this->hasHttpLink() ) { + if (preg_match('#^' . $this->storagePath . '(\d+)$#', $file, $match)) { + $file = $match[1]; + } + else{ + $file = rawurlencode(basename($file)); + } + + return $this->httpLink.$file; + } + + throw new UnsupportedMethodException('Don`t know how to return http link'); + } + + public function store ($localFile, $desiredName) { + $result = json_decode(parent::store($localFile, $desiredName)); + + return $result['file_name']; + } + + } diff --git a/main/Utils/Storage/Engines/StorageEngineWebDAV.class.php b/main/Utils/Storage/Engines/StorageEngineWebDAV.class.php new file mode 100644 index 0000000000..9c0268e661 --- /dev/null +++ b/main/Utils/Storage/Engines/StorageEngineWebDAV.class.php @@ -0,0 +1,89 @@ + + */ + +/** + * Class StorageEngineWebDav + */ +class StorageEngineWebDAV extends StorageEngineHTTP { + + protected $hasHttpLink = true; + protected $canReadRemote = false; + protected $ownNamingPolicy = true; + + protected $folderShardingDepth = 3; + + public function store ($file, $desiredName) { + $sendRequest = HttpRequest::create() + ->setMethod(HttpMethod::put()) + ->setUrl( HttpUrl::create()->parse($this->getUploadLink($desiredName)) ); + + /** @var CurlHttpResponse $resp */ + $curl = + CurlHttpClient::create() + ->setOption(CURLOPT_PUT, true) + ->setOption(CURLOPT_INFILE, fopen($file, 'r')) + ->setOption(CURLOPT_INFILESIZE, filesize($file)) + ->setOption(CURLOPT_TIMEOUT, 25); + if ( is_array($this->uploadOptions) && isset($this->uploadOptions['userpwd']) ) { + $curl + ->setOption(CURLOPT_HTTPAUTH, CURLAUTH_ANY) + ->setOption(CURLOPT_USERPWD, $this->uploadOptions['userpwd']); + } + + $upload = function() use ($curl, $sendRequest) { + $resp = $curl->send($sendRequest); + $status = $resp->getStatus()->getId(); + + if ( $status<200 || $status>=400 ) { + throw new MissingElementException("Got HTTP response code {$status}"); + } + }; + + $this->tryToDo($upload, "File ({$desiredName}) was not stored, reason: %s"); + + return $desiredName; + } + + protected function getUploadLink($file) { + if ( !empty($this->uploadUrl) ) { + return $this->uploadUrl.$this->generateSubPath($file).$file; + } + + throw new UnsupportedMethodException('Don`t know how to return http link'); + } + + protected function unlink($file) { + $sendRequest = HttpRequest::create() + ->setMethod(HttpMethod::delete()) + ->setUrl( HttpUrl::create()->parse($this->getUploadLink($file)) ); + + /** @var CurlHttpResponse $resp */ + $curl = + CurlHttpClient::create() + ->setOption(CURLOPT_CUSTOMREQUEST, "DELETE") + ->setOption(CURLOPT_TIMEOUT, 25); + if ( is_array($this->uploadOptions) && isset($this->uploadOptions['userpwd']) ) { + $curl + ->setOption(CURLOPT_HTTPAUTH, CURLAUTH_ANY) + ->setOption(CURLOPT_USERPWD, $this->uploadOptions['userpwd']); + } + + $delete = function() use ($curl, $sendRequest) { + $response = $curl->send($sendRequest); + $status = $response->getStatus()->getId(); + + if ($status<200 || $status>=400) { + throw new MissingElementException("Got HTTP response code {$status}"); + } + }; + + $this->tryToDo($delete, "File ({$file}) was not deleted, reason: %s"); + + return true; + } + + +} \ No newline at end of file diff --git a/main/Utils/Storage/StorableFile.class.php b/main/Utils/Storage/StorableFile.class.php new file mode 100644 index 0000000000..e3eba9aaf9 --- /dev/null +++ b/main/Utils/Storage/StorableFile.class.php @@ -0,0 +1,447 @@ + + * @date 2014.05.15 + */ + +abstract class StorableFile extends IdentifiableObject implements onBeforeSave, onAfterDrop { + + protected $fileName = null; + protected $mimeType = null; + protected $originalFileName = null; + protected $size = null; + protected $createDate = null; + protected $engineType = null; + protected $engineTypeId = null; + protected $engineConfig = null; + + protected $baseStorageEngineId = null; + protected $baseStorageEngineConfig =null; + protected $storageChanged = false; + protected $cloned = false; + protected $clonedFrom = 0; + protected $baseFileName = null; + protected $removed = false; + protected $storage = null; + + /** + * @return StorableFile + **/ + public static function create() { + return new static(); + } + + public function onBeforeSave() { + $this->applyStorageChanges(); + } + + public function onAfterDrop() { + $this->markRemoved(); + } + + /** + * @param $file array + * @return static + **/ + public static function createFromPost(array $file) + { + $upload_fields = array('name', 'type', 'size', 'tmp_name', 'error'); + $check = array_diff( array_keys($file), $upload_fields ); + + if ( !empty($check) ) { + throw new WrongArgumentException('Not an uploaded file given'); + } + + if ($file['error']>0) { + throw new Exception('Error in uploaded file', $file['error']); + } + + if (class_exists('finfo')) { + $finfo = new finfo(FILEINFO_MIME_TYPE); + $file['type'] = $finfo->file($file['tmp_name']); + } + + return static::create() + ->setOriginalFileName($file['name']) + ->setMimeType($file['type']) + ->setSize($file['size']) + ->setCreateDate(Timestamp::makeNow()) + ->setFileName($file['tmp_name']) + ->setEngineTypeId(StorageEngineType::TMP) + ->setEngineType(self::getDefaultStorage()); + } + + /** + * @param $url string + * @return static + * @throws WrongArgumentException + */ + public static function createFromUrl($url) { + if (!preg_match('@^(https?|ftp)://@', $url)) { + throw new WrongArgumentException('not a valid URL: ' . $url); + } + return static::create() + ->setOriginalFileName($url) + ->setCreateDate(Timestamp::makeNow()) + ->setFileName($url) + ->setEngineTypeId(StorageEngineType::URL) + ->setEngineType(self::getDefaultStorage()); + } + + /** + * @return StorageEngineType + **/ + public static function getDefaultStorage() { + try { + $default = StorageConfig::me()->getDefaultEngine(); + } + catch(Exception $e) { + throw new Exception('No default storage found', 0, $e); + } + + return $default; + } + + + public function getFileName() { + return $this->fileName; + } + + /** @return static **/ + public function setFileName($fileName) + { + if ($fileName === null||!strlen($fileName)) { + throw new WrongArgumentException('File name can not be empty'); + } + if ($this->baseFileName === null) { + $this->baseFileName = $fileName; + } + + $this->fileName = $fileName; + + return $this; + } + + public function getMimeType() + { + return $this->mimeType; + } + + /** @return static **/ + public function setMimeType($mimeType) { + $this->mimeType = $mimeType; + + return $this; + } + + public function getOriginalFileName() { + return $this->originalFileName; + } + + /** @return static **/ + public function setOriginalFileName($originalFileName) { + $this->originalFileName = $originalFileName; + + return $this; + } + + public function getSize() { + return $this->size; + } + + /** @return static **/ + public function setSize($size) { + $this->size = $size; + return $this; + } + + /** @return Timestamp **/ + public function getCreateDate() { + return $this->createDate; + } + + /** @return static **/ + public function setCreateDate(Timestamp $createDate) { + $this->createDate = $createDate; + return $this; + } + + /** @return static **/ + public function dropCreateDate() { + $this->createDate = null; + return $this; + } + + /** @return StorageEngineType **/ + public function getEngineType() { + if (!$this->engineType && $this->engineTypeId) { + $this->engineType = new StorageEngineType($this->engineTypeId); + } + + return $this->engineType; + } + + public function getEngineTypeId() { + return $this->engineType + ? $this->engineType->getId() + : $this->engineTypeId; + } + + /** @return static **/ + public function setEngineType(StorageEngineType $engineType) { + $this->parseEngineId($engineType->getId()); + + $this->engineType = $engineType; + $this->engineTypeId = $engineType->getId(); + + return $this; + } + + /** @return static **/ + public function setEngineTypeId($id) + { + $this->parseEngineId($id); + + $this->engineType = null; + $this->engineTypeId = $id; + + return $this; + } + + /** @return static **/ + public function dropEngineType() + { + $this->engineType = null; + $this->engineTypeId = null; + + return $this; + } + + public function getEngineConfig() + { + return $this->engineConfig; + } + + /** @return static **/ + public function setEngineConfig($engineConfig) + { + $this->engineConfig = $engineConfig; + + if ( $this->baseStorageEngineConfig === null && !$this->storageChanged ) { + $this->baseStorageEngineConfig = $this->engineConfig; + } + + if ($this->baseStorageEngineConfig != $this->engineConfig) { + $this->storageChanged = true; + } + + return $this; + } + + + public function isStorageChanged() { + return $this->storageChanged; + } + + protected function getBaseStorageEngineTypeId() { + return $this->baseStorageEngineId; + } + + protected function getBaseStorageEngineType() { + return StorageEngineType::create($this->baseStorageEngineId); + } + + protected function parseEngineId($id) { + if ( $this->baseStorageEngineId === null ) { + $this->baseStorageEngineId = $id; + if ($this->baseStorageEngineConfig === null) { + $this->baseStorageEngineConfig = $this->engineConfig; + } + } + elseif ($this->baseStorageEngineId != $id) { + $this->storageChanged = true; + } + $this->engineConfig = null; + } + + public function getBaseStorageEngineConfig() { + if ( !$this->storageChanged ) { + return $this->engineConfig; + } + else { + return $this->baseStorageEngineConfig; + } + } + + public function markRemoved($removed=true) { + $this->removed = (bool)$removed; + } + + public function generateName() { + $name = str_replace('.', '', microtime(true)); + if ($this->mimeType) { + try { + $mime = MimeType::getByMimeType($this->mimeType); + $name .= '.' . $mime->getExtension(); + } catch (MissingElementException $e) { + // no extension + } + } + return $name; + } + + public function isRenamed() { + return $this->baseFileName !== $this->fileName; + } + + public function isRemoved() { + return $this->removed; + } + + public function isCloned() { + return $this->cloned; + } + + protected function getBaseFileName() { + if ($this->baseFileName!==null) { + return $this->baseFileName; + } + return $this->getFileName(); + } + + public function getLink($stripScheme = false) { + $link = StorageEngine::create(StorageEngineType::create($this->getBaseStorageEngineTypeId()), $this->getBaseStorageEngineConfig()) + ->getHttpLink( $this->getBaseFileName() ); + if( $stripScheme ) { + $link = str_replace(['http:', 'https:'], '', $link); + } + return $link; + } + + public function getFile() { + if (!$this->storage && $this->getBaseStorageEngineTypeId() !== null ) { + $this->storage = StorageEngine::create(StorageEngineType::create($this->getBaseStorageEngineTypeId()), $this->getBaseStorageEngineConfig()); + } + return $this->storage + ->get( $this->getBaseFileName() ); + } + + public function applyStorageChanges() { + if ( + !$this->storageChanged // Если хранилище не изменилось + && !$this->isCloned() // не было копирования + && !$this->isRenamed() // и не меняли имя, + ) { + return $this; // то нам делать нечего. + } + + $to = StorageEngine::create( StorageEngineType::create($this->engineTypeId), $this->engineConfig ); + + if (!$this->storageChanged) { + // Скопировали в то же хранилище + $from = $to; + } + else { + $from = StorageEngine::create( StorageEngineType::create($this->baseStorageEngineId), $this->baseStorageEngineConfig ); + } + + $oldName = $this->getBaseFileName(); + if (!$this->isRenamed()) { + $desiredName = ($oldName // Если есть старое имя, + && !$this->isCloned() // не было копирования + && !$from->hasOwnNamingPolicy())? // и исходное хранилище поддерживает наши имена, + $oldName: // тогда можно использовать это имя, + $this->generateName(); // иначе - сгенерировать новое + } + else { + $desiredName = $this->getFileName(); + } + + $noNeedToUnlink = false; + + if ( + $this->isRenamed() && // Если дали новое имя, + !$this->isCloned() && // не копировали, + !$this->storageChanged && // не менялось хранилище + $from->canRename() // и можем переименовывать - + ) { + $from->rename($oldName, $desiredName); + $noNeedToUnlink = true; + $newName = $desiredName; + } + else { + if ($from === $to && $to->canCopy()) { + $newName = $to->copy($oldName, $desiredName); + } + else { + if ($from->hasHttpLink() && $to->canReadRemote()) { + $newName = $to->storeRemote( $from->getHttpLink($oldName), $desiredName ); + } + else { + $newName = $to->store( $from->get($oldName), $desiredName ); + } + } + } + + if (!$this->createDate) { + $this->createDate = Timestamp::makeNow(); + } + + if (!$this->mimeType && !$this->size) { + $data = $from->stat($oldName); + $this->setMimeType($data['mime']); + $this->setSize($data['size']); + } + + if ( !$this->isCloned() && $to->isTrusted() && !$noNeedToUnlink ) { + $from->remove($oldName); + } + + $this->setFileName($newName); + + $this->baseStorageEngineConfig = $this->engineConfig; + $this->baseStorageEngineId = $this->engineTypeId; + $this->baseFileName = $this->getFileName(); + $this->storageChanged = false; + $this->cloned = false; + $this->clonedFrom = 0; + $this->removed = false; + + return $this; + } + + public function __clone() { + $vars = get_object_vars($this); + foreach ($vars as $key => $val) { + if (is_object($val)) { + $this->{$key} = clone $val; + } + } + $this->createDate = Timestamp::makeNow(); + $this->cloned = true; + $this->clonedFrom = $this->id? + $this->id: + $this->clonedFrom; + $this->setId(null); + $this->removed = false; + } + + public function __destruct() { + if ($this->isRemoved()) { + try { + $engine = $this->engineTypeId; + $config = $this->engineConfig; + if ($this->baseStorageEngineId) { + // Меняли, но не сохранили + $engine = $this->baseStorageEngineId; + $config = $this->baseStorageEngineConfig; + } + $storage = StorageEngine::create( StorageEngineType::create($engine), $config ); + $storage->remove($this->getFileName()); + } + catch(Exception $e) {} + } + } + +} \ No newline at end of file diff --git a/main/Utils/Storage/StorageConfig.class.php b/main/Utils/Storage/StorageConfig.class.php new file mode 100644 index 0000000000..e1373e5ab5 --- /dev/null +++ b/main/Utils/Storage/StorageConfig.class.php @@ -0,0 +1,46 @@ + + * @date 2014.05.18 + */ + +class StorageConfig extends Singleton{ + + protected $configs = array(); + + protected $default = null; + + /** @return StorageConfig */ + public static function me() + { + return Singleton::getInstance(__CLASS__); + } + + public function addConfig(StorageEngineType $type, $link, $config) { + if(!$config) { + if(isset($this->configs[$type->getId()][$link])) { + unset($this->configs[$type->getId()][$link]); + } + } else { + $this->configs[$type->getId()][$link] = $config; + } + } + + public function getConfig(StorageEngineType $type, $link) { + if(!isset($this->configs[$type->getId()][$link])) { + return array(); + } + return $this->configs[$type->getId()][$link]; + } + + public function setDefaultEngine(StorageEngineType $engine) { + $this->default = $engine; + return $this; + } + + public function getDefaultEngine() { + return $this->default; + } + +} \ No newline at end of file diff --git a/main/Utils/Storage/StorageEngineType.class.php b/main/Utils/Storage/StorageEngineType.class.php new file mode 100644 index 0000000000..e957ce5b5a --- /dev/null +++ b/main/Utils/Storage/StorageEngineType.class.php @@ -0,0 +1,103 @@ + 'StorageEngine', + self::UPLOADED => 'StorageEngineUploadedStatic', + self::URL => 'StorageEngineURL', + self::WEBDAV => 'StorageEngineWebDAV', + self::FTP => 'StorageEngineFTP', + self::IMAGESHACK => 'StorageEngineImageShack', + self::LOCAL => 'StorageEngineLocal', + ); + + /** + * @staticx + * @param $id + * @return StorageEngineType + */ + public static function create($id) { + return new static($id); + } + + /** + * @static + * @param $var String|Object + * @return StorageEngineType + */ + public static function getByClass($var){ + $id = false; + if( gettype($var) == 'object' ){ + $var = get_class($var); + } + + if( is_scalar($var) ){ + $id = array_search( $var, self::makeNameList(), true); + } + + if($id === false) { + throw new MissingElementException('No such Storage Engine: ' . $var); + } + + return self::create($id); + } + + /** + * @return StorageEngineType + */ + public static function tmp() { + return new self(self::TMP); + } + + /** + * @return StorageEngineType + */ + public static function uploaded() { + return new self(self::UPLOADED); + } + + /** + * @return StorageEngineType + */ + public static function url() { + return new self(self::URL); + } + + /** + * @return StorageEngineType + */ + public static function webdav() { + return new self(self::WEBDAV); + } + + /** + * @return StorageEngineType + */ + public static function ftp() { + return new self(self::FTP); + } + + /** + * @return StorageEngineType + */ + public static function imageShack() { + return new self(self::IMAGESHACK); + } + + } +?> \ No newline at end of file diff --git a/main/Utils/SynchronizableObject.class.php b/main/Utils/SynchronizableObject.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TextUtils.class.php b/main/Utils/TextUtils.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TidyValidator.class.php b/main/Utils/TidyValidator.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TuringTest/BackgroundDrawer.class.php b/main/Utils/TuringTest/BackgroundDrawer.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TuringTest/CellBackgroundDrawer.class.php b/main/Utils/TuringTest/CellBackgroundDrawer.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TuringTest/CircleBackgroundDrawer.class.php b/main/Utils/TuringTest/CircleBackgroundDrawer.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TuringTest/CodeGenerator.class.php b/main/Utils/TuringTest/CodeGenerator.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TuringTest/Color.class.php b/main/Utils/TuringTest/Color.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TuringTest/ColorArray.class.php b/main/Utils/TuringTest/ColorArray.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TuringTest/CurvedStringDrawer.class.php b/main/Utils/TuringTest/CurvedStringDrawer.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TuringTest/Drawer.class.php b/main/Utils/TuringTest/Drawer.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TuringTest/ErrorDrawer.class.php b/main/Utils/TuringTest/ErrorDrawer.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TuringTest/InclinedStringDrawer.class.php b/main/Utils/TuringTest/InclinedStringDrawer.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TuringTest/LinearStringDrawer.class.php b/main/Utils/TuringTest/LinearStringDrawer.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TuringTest/RandomLinesBackgroundDrawer.class.php b/main/Utils/TuringTest/RandomLinesBackgroundDrawer.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TuringTest/TextDrawer.class.php b/main/Utils/TuringTest/TextDrawer.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TuringTest/TuringImage.class.php b/main/Utils/TuringTest/TuringImage.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TuringTest/WavesBackgroundDrawer.class.php b/main/Utils/TuringTest/WavesBackgroundDrawer.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/TypesUtils.class.php b/main/Utils/TypesUtils.class.php old mode 100644 new mode 100755 diff --git a/main/Utils/UuidUtils.class.php b/main/Utils/UuidUtils.class.php new file mode 100755 index 0000000000..0093f66cc3 --- /dev/null +++ b/main/Utils/UuidUtils.class.php @@ -0,0 +1,48 @@ + @@ -44,14 +44,14 @@ function init() define('ONPHP_META_BUILDERS', ONPHP_META_PATH.'builders'.DIRECTORY_SEPARATOR); define('ONPHP_META_PATTERNS', ONPHP_META_PATH.'patterns'.DIRECTORY_SEPARATOR); define('ONPHP_META_TYPES', ONPHP_META_PATH.'types'.DIRECTORY_SEPARATOR); - + set_include_path( get_include_path().PATH_SEPARATOR .ONPHP_META_BUILDERS.PATH_SEPARATOR .ONPHP_META_PATTERNS.PATH_SEPARATOR .ONPHP_META_TYPES.PATH_SEPARATOR ); - + if (!defined('ONPHP_META_DAO_DIR')) define( 'ONPHP_META_DAO_DIR', @@ -203,18 +203,17 @@ function stop($message = null) .DIRECTORY_SEPARATOR .'classes' .DIRECTORY_SEPARATOR; - + include_once $metaRoot.'ConsoleMode.class.php'; include_once $metaRoot.'MetaOutput.class.php'; include_once $metaRoot.'TextOutput.class.php'; include_once $metaRoot.'ColoredTextOutput.class.php'; - + if ( isset($_SERVER['TERM']) - && ( - $_SERVER['TERM'] == 'xterm' - || $_SERVER['TERM'] == 'linux' - ) + && in_array($_SERVER['TERM'], array( + 'xterm', 'linux', 'screen', 'screen-256color', 'xterm-256color', 'cygwin' + )) && !$metaNoColor ) { $out = new ColoredTextOutput(); @@ -283,36 +282,36 @@ function stop($message = null) newLine(); try { - $meta = - MetaConfiguration::me()-> - setOutput($out)-> - load(ONPHP_META_PATH.'internal.xml', false); + $meta = MetaConfiguration::me() + ->setOutput($out) + ->load(ONPHP_META_PATH.'internal.xml'); $out->info('Known internal classes: '); - foreach ($meta->getClassList() as $class) { + foreach ($meta->getCorePlugin()->getClassList() as $class) { $out->info($class->getName().', ', true); } $out->infoLine("that's all.")->newLine(); - $meta-> - setDryRun($metaDryRun)-> - load($pathMeta)-> - setForcedGeneration($metaForce); - - if ($metaOnlyContainers) { - $meta->buildContainers(); - } else { - $meta-> - buildClasses()-> - buildContainers(); - - if (!$metaNoSchema) - $meta->buildSchema(); - - if (!$metaNoSchemaCheck) - $meta->buildSchemaChanges(); - } - + $meta + ->setDryRun($metaDryRun) + ->setForcedGeneration($metaForce) + ->load($pathMeta); + + + if ($metaOnlyContainers) { + $meta->getCorePlugin() + ->setBuildClasses(false) + ->setBuildSchema(false) + ->setBuildSchemaChanges(false) + ; + } else { + $meta->getCorePlugin() + ->setBuildSchema(!$metaNoSchema) + ->setBuildSchemaChanges(!$metaNoSchemaCheck) + ; + } + + $meta->buildFiles(); $meta->checkForStaleFiles($metaDropStaleFiles); $out->newLine()->info('Trying to compile all known classes... '); @@ -320,20 +319,21 @@ function stop($message = null) ClassUtils::preloadAllClasses(); $out->infoLine('done.'); - - if ($metaCheckEnumerationRefIntegrity) - $meta->setWithEnumerationRefIntegrityCheck(true); - - if (!$metaNoIntegrityCheck) - $meta->checkIntegrity(); - } catch (BaseException $e) { - $out-> - newLine()-> - errorLine($e->getMessage(), true)-> - newLine()-> - logLine( - $e->getTraceAsString() - ); + + if (!$metaNoIntegrityCheck) { + if ($metaCheckEnumerationRefIntegrity) { + $meta->getCorePlugin()->setWithEnumerationRefIntegrityCheck(true); + } + + $meta->checkIntegrity(); + } + + } catch (Exception $e) { + $out + ->newLine() + ->errorLine($e->getMessage(), true) + ->newLine() + ->logLine($e->getTraceAsString()); } } else { $out->getOutput()->resetAll()->newLine(); diff --git a/meta/builders/AutoClassBuilder.class.php b/meta/builders/AutoClassBuilder.class.php old mode 100644 new mode 100755 index f0995ec7bb..dcdab4d968 --- a/meta/builders/AutoClassBuilder.class.php +++ b/meta/builders/AutoClassBuilder.class.php @@ -17,11 +17,11 @@ final class AutoClassBuilder extends BaseBuilder public static function build(MetaClass $class) { $out = self::getHead(); - + $out .= "abstract class Auto{$class->getName()}"; - + $isNamed = false; - + if ($parent = $class->getParent()) $out .= " extends {$parent->getName()}"; elseif ( @@ -30,30 +30,36 @@ public static function build(MetaClass $class) ) { $out .= " extends NamedObject"; $isNamed = true; - } elseif (!$class->getPattern() instanceof ValueObjectPattern) + } elseif ( + $class->getPattern() instanceof NoSqlClassPattern + && $class->hasProperty('id') + ) { + $out .= " extends NoSqlObject"; + } elseif (!$class->getPattern() instanceof ValueObjectPattern) { $out .= " extends IdentifiableObject"; - + } + if ($interfaces = $class->getInterfaces()) $out .= ' implements '.implode(', ', $interfaces); - + $out .= "\n{\n"; - + foreach ($class->getProperties() as $property) { if (!self::doPropertyBuild($class, $property, $isNamed)) continue; - + $out .= "protected \${$property->getName()} = " ."{$property->getType()->getDeclaration()};\n"; - + if ($property->getFetchStrategyId() == FetchStrategy::LAZY) { $out .= "protected \${$property->getName()}Id = null;\n"; } } - + $valueObjects = array(); - + foreach ($class->getProperties() as $property) { if ( $property->getType() instanceof ObjectType @@ -65,7 +71,7 @@ public static function build(MetaClass $class) $property->getType()->getClassName(); } } - + if ($valueObjects) { $out .= << $className) { $out .= "\$this->{$propertyName} = new {$className}();\n"; } - + $out .= "}\n"; } - + + /** @var $property MetaClassProperty */ foreach ($class->getProperties() as $property) { if (!self::doPropertyBuild($class, $property, $isNamed)) continue; - + $out .= $property->toMethods($class); } - + $out .= "}\n"; $out .= self::getHeel(); - + return $out; } - + private static function doPropertyBuild( MetaClass $class, MetaClassProperty $property, @@ -113,19 +120,19 @@ private static function doPropertyBuild( ) ) return true; - + return false; } - + if ($isNamed && $property->getName() == 'name') return false; - + if ( ($property->getName() == 'id') && !$property->getClass()->getParent() ) return false; - + // do not redefine parent's properties if ( $property->getClass()->getParent() @@ -135,7 +142,7 @@ private static function doPropertyBuild( ) ) return false; - + return true; } } diff --git a/meta/builders/AutoDaoBuilder.class.php b/meta/builders/AutoDaoBuilder.class.php old mode 100644 new mode 100755 diff --git a/meta/builders/AutoEmptyDaoBuilder.class.php b/meta/builders/AutoEmptyDaoBuilder.class.php new file mode 100644 index 0000000000..b4ef0ba399 --- /dev/null +++ b/meta/builders/AutoEmptyDaoBuilder.class.php @@ -0,0 +1,38 @@ + + * @date 2013.04.19 + */ + +/** + * @ingroup Builders + */ +final class AutoEmptyDaoBuilder extends BaseBuilder { + + public static function build(MetaClass $class) + { + if ( + is_null( $class->getParent() ) + || + $class->getParent()->getPattern() instanceof InternalClassPattern + ) { + $parentName = 'EmptyDAO'; + } else { + $parentName = $class->getParent()->getName().'DAO'; + } + + $out = self::getHead(); + + $out .= <<getName()}DAO extends {$parentName} +{ + +EOT; + + $out .= self::buildPointers($class)."\n}\n"; + + return $out.self::getHeel(); + } + +} diff --git a/meta/builders/AutoNoSqlDaoBuilder.class.php b/meta/builders/AutoNoSqlDaoBuilder.class.php new file mode 100755 index 0000000000..46ab06806e --- /dev/null +++ b/meta/builders/AutoNoSqlDaoBuilder.class.php @@ -0,0 +1,43 @@ +getParent() ) + || + $class->getParent()->getPattern() instanceof InternalClassPattern + ) { + $parentName = 'NoSqlDAO'; + } else { + $parentName = $class->getParent()->getName().'DAO'; + } + + $out = self::getHead(); + + $out .= <<getName()}DAO extends {$parentName} +{ + +EOT; + + $out .= self::buildPointers($class)."\n}\n"; + + return $out.self::getHeel(); + } + +} diff --git a/meta/builders/AutoProtoClassBuilder.class.php b/meta/builders/AutoProtoClassBuilder.class.php old mode 100644 new mode 100755 diff --git a/meta/builders/BaseBuilder.class.php b/meta/builders/BaseBuilder.class.php old mode 100644 new mode 100755 index 9efc4c85ca..c5e6627c0b --- a/meta/builders/BaseBuilder.class.php +++ b/meta/builders/BaseBuilder.class.php @@ -18,11 +18,11 @@ public static function build(MetaClass $class) { throw new UnimplementedFeatureException('i am forgotten method'); } - + protected static function buildPointers(MetaClass $class) { $out = null; - + if (!$class->getPattern() instanceof AbstractClassPattern) { if ( $class->getIdentifier()->getColumnName() !== 'id' @@ -35,7 +35,7 @@ public function getIdName() EOT; } - + $out .= <<getName()}'; } + +EOT; + + + if( + $class->getIdentifier()->getType() instanceof UuidType + ) + { + $out .= <<getTableName()}_id'; } EOT; + } + } elseif ($class->getWithInternalProperties()) { $out .= <<getReferencingClasses()) { $uncachers = array(); foreach ($liaisons as $className) { - $uncachers[] = $className.'::dao()->uncacheLists();'; + if( method_exists($className,'dao') ) { + $uncachers[] = $className.'::dao()->uncacheLists();'; + } } - + $uncachers = implode("\n", $uncachers); - + $out .= <<getSourceLink()) { $out .= <<'; diff --git a/meta/builders/BusinessClassBuilder.class.php b/meta/builders/BusinessClassBuilder.class.php old mode 100644 new mode 100755 diff --git a/meta/builders/ContainerClassBuilder.class.php b/meta/builders/ContainerClassBuilder.class.php old mode 100644 new mode 100755 index eb3243e118..ce9da8d5e1 --- a/meta/builders/ContainerClassBuilder.class.php +++ b/meta/builders/ContainerClassBuilder.class.php @@ -18,27 +18,30 @@ public static function build(MetaClass $class) { throw new UnsupportedMethodException(); } - + public static function buildContainer( MetaClass $class, MetaClassProperty $holder ) { $out = self::getHead(); - + $containerName = $class->getName().ucfirst($holder->getName()).'DAO'; - + + $isNoSQL = is_subclass_of($holder->getType()->getClassName(), 'NoSqlObject'); + $parentExtend = $isNoSQL ? 'NoSql' : ''; + $out .= 'final class ' .$containerName .' extends ' - .$holder->getRelation()->toString().'Linked' + .$holder->getRelation()->toString().$parentExtend.'Linked' ."\n{\n"; $className = $class->getName(); $propertyName = strtolower($className[0]).substr($className, 1); - + $remoteColumnName = $holder->getType()->getClass()->getTableName(); - + $out .= <<getRelation()->getId() == MetaRelation::MANY_TO_MANY) { - $out .= <<getTableName().'_'.$remoteColumnName; + if( strcmp($class->getTableName(), $remoteColumnName)>=0 ) { + $helper_table_name = $remoteColumnName.'_'.$class->getTableName(); + } + $out .= <<getTableName()}_{$remoteColumnName}'; + return '{$helper_table_name}'; } public function getChildIdField() @@ -74,7 +81,7 @@ public function getChildIdField() EOT; } - + $out .= <<getType()) + $type = "{$type->getName()} "; + else + $type = null; + + $out .= <<getName()} extends Enum +{ + // implement me! + protected static \$names = array(); +} + +EOT; + + return $out.self::getHeel(); + } + } +?> \ No newline at end of file diff --git a/meta/builders/EnumerationClassBuilder.class.php b/meta/builders/EnumerationClassBuilder.class.php old mode 100644 new mode 100755 diff --git a/meta/builders/OnceBuilder.class.php b/meta/builders/OnceBuilder.class.php old mode 100644 new mode 100755 diff --git a/meta/builders/ProtoClassBuilder.class.php b/meta/builders/ProtoClassBuilder.class.php old mode 100644 new mode 100755 diff --git a/meta/builders/RegistryClassBuilder.class.php b/meta/builders/RegistryClassBuilder.class.php new file mode 100644 index 0000000000..d3dfeb813b --- /dev/null +++ b/meta/builders/RegistryClassBuilder.class.php @@ -0,0 +1,37 @@ +getType()) + $type = "{$type->getName()} "; + else + $type = null; + + $out .= <<getName()} extends Registry +{ + // implement me! + protected static \$names = array(); +} + +EOT; + + return $out.self::getHeel(); + } + } \ No newline at end of file diff --git a/meta/builders/SchemaBuilder.class.php b/meta/builders/SchemaBuilder.class.php old mode 100644 new mode 100755 index 6c587c29bc..cb9ffe713b --- a/meta/builders/SchemaBuilder.class.php +++ b/meta/builders/SchemaBuilder.class.php @@ -14,6 +14,8 @@ **/ final class SchemaBuilder extends BaseBuilder { + protected static $knownTables = array(); + public static function buildTable($tableName, array $propertyList) { $out = <<getRelation() @@ -32,69 +34,82 @@ public static function buildTable($tableName, array $propertyList) ) { continue; } - + $column = $property->toColumn(); - + if (is_array($column)) $columns = array_merge($columns, $column); else $columns[] = $property->toColumn(); } - + $out .= implode("->\n", $columns); - + return $out."\n);\n\n"; } - + public static function buildRelations(MetaClass $class) { $out = null; - + $knownJunctions = array(); - + foreach ($class->getAllProperties() as $property) { if ($relation = $property->getRelation()) { - + + if( !$property->isBuildReference() ) { + continue; + } + $foreignClass = $property->getType()->getClass(); - + if ( $relation->getId() == MetaRelation::ONE_TO_MANY // nothing to build, it's in the same table // or table does not exist at all || !$foreignClass->getPattern()->tableExists() // no need to process them - || $class->getParent() + || !$class->getPattern()->tableExists() ) { continue; } elseif ( $relation->getId() == MetaRelation::MANY_TO_MANY ) { - $tableName = - $class->getTableName() - .'_' - .$foreignClass->getTableName(); - - if (isset($knownJunctions[$tableName])) + if( strcmp($class->getTableName(), $foreignClass->getTableName())>=0 ) { + $tableName = + $foreignClass->getTableName() + .'_' + .$class->getTableName(); + } else { + $tableName = + $class->getTableName() + .'_' + .$foreignClass->getTableName(); + } + + if (isset($knownJunctions[$tableName]) || isset(self::$knownTables[$tableName])) continue; // collision prevention - else + else { $knownJunctions[$tableName] = true; - + self::$knownTables[$tableName] = true; + } + $foreignPropery = clone $foreignClass->getIdentifier(); - + $name = $class->getName(); $name = strtolower($name[0]).substr($name, 1); $name .= 'Id'; - + $foreignPropery-> setName($name)-> setColumnName($foreignPropery->getConvertedName())-> // we don't need primary key here setIdentifier(false); - + // we don't want any garbage in such tables $property = clone $property; $property->required(); - + // prevent name collisions if ( $property->getRelationColumnName() @@ -105,7 +120,7 @@ public static function buildRelations(MetaClass $class) .$property->getConvertedName().'_id' ); } - + $out .= << addTable( @@ -117,13 +132,54 @@ public static function buildRelations(MetaClass $class) EOT; + + $sourceColumn = $property->getRelationColumnName(); + $targetTable = $foreignClass->getTableName(); + $targetColumn = $foreignClass->getIdentifier()->getColumnName(); + + $out .= << {$targetTable}.{$targetColumn} +\$schema-> + getTableByName('{$tableName}')-> + getColumnByName('{$sourceColumn}')-> + setReference( + \$schema-> + getTableByName('{$targetTable}')-> + getColumnByName('{$targetColumn}'), + ForeignChangeAction::cascade(), + ForeignChangeAction::cascade() + ); + + +EOT; + + $sourceColumn = $foreignPropery->getRelationColumnName(); + $targetTable = $class->getTableName(); + $targetColumn = $class->getIdentifier()->getColumnName(); + + $out .= << {$targetTable}.{$targetColumn} +\$schema-> + getTableByName('{$tableName}')-> + getColumnByName('{$sourceColumn}')-> + setReference( + \$schema-> + getTableByName('{$targetTable}')-> + getColumnByName('{$targetColumn}'), + ForeignChangeAction::cascade(), + ForeignChangeAction::cascade() + ); + + +EOT; + } else { $sourceTable = $class->getTableName(); $sourceColumn = $property->getRelationColumnName(); - + $targetTable = $foreignClass->getTableName(); $targetColumn = $foreignClass->getIdentifier()->getColumnName(); - + $out .= << {$targetTable}.{$targetColumn} \$schema-> @@ -139,20 +195,20 @@ public static function buildRelations(MetaClass $class) EOT; - + } } } - + return $out; } - + public static function getHead() { $out = parent::getHead(); - + $out .= "\$schema = new DBSchema();\n\n"; - + return $out; } } diff --git a/meta/classes/ColoredTextOutput.class.php b/meta/classes/ColoredTextOutput.class.php old mode 100644 new mode 100755 diff --git a/meta/classes/ConsoleMode.class.php b/meta/classes/ConsoleMode.class.php old mode 100644 new mode 100755 diff --git a/meta/classes/Format.class.php b/meta/classes/Format.class.php old mode 100644 new mode 100755 diff --git a/meta/classes/MetaClass.class.php b/meta/classes/MetaClass.class.php old mode 100644 new mode 100755 index 148898d532..1e3fffd780 --- a/meta/classes/MetaClass.class.php +++ b/meta/classes/MetaClass.class.php @@ -14,23 +14,33 @@ **/ class MetaClass { + /** @var string */ private $name = null; + /** @var string */ private $tableName = null; + /** @var MetaClassType */ private $type = null; - + /** @var MetaClass */ private $parent = null; - + + /** @var MetaClassProperty[] */ private $properties = array(); + /** @var string[] */ private $interfaces = array(); + /** @var bool[] (string)className => (bool)isReferenced */ private $references = array(); - + /** @var GenerationPattern */ private $pattern = null; + /** @var MetaClassProperty */ private $identifier = null; - + + /** @var string */ private $source = null; - + + /** @var FetchStrategy */ private $strategy = null; - + + /** @var bool */ private $build = true; public function __construct($name) @@ -347,7 +357,7 @@ public function getFetchStrategyId() public function hasChilds() { - foreach (MetaConfiguration::me()->getClassList() as $class) { + foreach (MetaConfiguration::me()->getCorePlugin()->getClassList() as $class) { if ( $class->getParent() && $class->getParent()->getName() == $this->getName() @@ -372,6 +382,7 @@ public function doBuild() } /** + * @param bool $do * @return MetaClass **/ public function setBuild($do) @@ -382,6 +393,7 @@ public function setBuild($do) } /** + * @param string $name * @return MetaClassProperty **/ public function isRedefinedProperty($name) diff --git a/meta/classes/MetaClassProperty.class.php b/meta/classes/MetaClassProperty.class.php old mode 100644 new mode 100755 index dbac6fe23b..a1ef8cf62a --- a/meta/classes/MetaClassProperty.class.php +++ b/meta/classes/MetaClassProperty.class.php @@ -15,20 +15,23 @@ class MetaClassProperty { private $class = null; - + private $name = null; private $columnName = null; - + + /** @var BasePropertyType|ObjectType|null */ private $type = null; private $size = null; - + private $required = false; private $identifier = false; - + /** @var MetaRelation */ private $relation = null; - + private $strategy = null; - + + private $reference = false; + public function __construct( $name, BasePropertyType $type, @@ -36,12 +39,12 @@ public function __construct( ) { $this->name = $name; - + $this->type = $type; - + $this->class = $class; } - + public function equals(MetaClassProperty $property) { return ( @@ -54,7 +57,7 @@ public function equals(MetaClassProperty $property) && ($property->isIdentifier() == $this->isIdentifier()) ); } - + /** * @return MetaClass **/ @@ -62,39 +65,39 @@ public function getClass() { return $this->class; } - + public function getName() { return $this->name; } - + /** * @return MetaClassProperty **/ public function setName($name) { $this->name = $name; - + return $this; } - + public function getColumnName() { return $this->columnName; } - + /** * @return MetaClassProperty **/ public function setColumnName($name) { $this->columnName = $name; - + return $this; } - + /** - * @return MetaClassProperty + * @return string **/ public function getConvertedName() { @@ -102,20 +105,20 @@ public function getConvertedName() preg_replace(':([A-Z]):', '_\1', $this->name) ); } - + /** - * @return BasePropertyType + * @return BasePropertyType|ObjectType **/ public function getType() { return $this->type; } - + public function getSize() { return $this->size; } - + /** * @throws WrongArgumentException * @return MetaClassProperty @@ -125,16 +128,16 @@ public function setSize($size) if ($this->type instanceof NumericType) { if (strpos($size, ',') !== false) { list($size, $precision) = explode(',', $size, 2); - + $this->type->setPrecision($precision); } } - + Assert::isInteger( $size, 'only integers allowed in size parameter' ); - + if ($this->type->isMeasurable()) { $this->size = $size; } else @@ -143,55 +146,55 @@ public function setSize($size) .$this->getName().'::'.get_class($this->type) ."' type" ); - + return $this; } - + public function isRequired() { return $this->required; } - + public function isOptional() { return !$this->required; } - + /** * @return MetaClassProperty **/ public function required() { $this->required = true; - + return $this; } - + /** * @return MetaClassProperty **/ public function optional() { $this->required = false; - + return $this; } - + public function isIdentifier() { return $this->identifier; } - + /** * @return MetaClassProperty **/ public function setIdentifier($really = false) { $this->identifier = ($really === true); - + return $this; } - + /** * @return MetaRelation **/ @@ -199,35 +202,35 @@ public function getRelation() { return $this->relation; } - + public function getRelationId() { if ($this->relation) return $this->relation->getId(); - + return null; } - + /** * @return MetaClassProperty **/ public function setRelation(MetaRelation $relation) { $this->relation = $relation; - + return $this; } - + /** * @return MetaClassProperty **/ public function setFetchStrategy(FetchStrategy $strategy) { $this->strategy = $strategy; - + return $this; } - + /** * @return FetchStrategy **/ @@ -235,7 +238,7 @@ public function getFetchStrategy() { return $this->strategy; } - + public function getFetchStrategyId() { if ($this->strategy) @@ -247,10 +250,37 @@ public function getFetchStrategyId() && (!$this->getType()->isGeneric()) ) return $this->getClass()->getFetchStrategyId(); - + return null; } - + + /** + * @return MetaClassProperty + **/ + public function buildReference() { + $this->reference = true; + + return $this; + } + + /** + * @return MetaClassProperty + **/ + public function skipReference() { + $this->reference = false; + + return $this; + } + + /** + * @return boolean + */ + public function isBuildReference() { + return $this->reference; + } + + + public function toMethods( MetaClass $class, MetaClassProperty $holder = null @@ -258,7 +288,7 @@ public function toMethods( { return $this->type->toMethods($class, $this, $holder); } - + public function getRelationColumnName() { if ($this->type instanceof ObjectType && !$this->type->isGeneric()) { @@ -274,10 +304,10 @@ public function getRelationColumnName() return $out; } else $columnName = $this->getColumnName(); - + return $columnName; } - + public function toColumn() { if ( @@ -294,40 +324,46 @@ public function toColumn() ) ) { $columns = array(); - + $prefix = $this->getType() instanceof InternalType ? $this->getColumnName().'_' : null; - + $remote = $this->getType()->getClass(); - + foreach ($remote->getAllProperties() as $property) { $columns[] = $property->buildColumn( $prefix.$property->getRelationColumnName() ); } - + return $columns; } - + return $this->buildColumn($this->getRelationColumnName()); } - + public function toLightProperty(MetaClass $holder) { $className = null; - + if ( ($this->getRelationId() == MetaRelation::ONE_TO_MANY) || ($this->getRelationId() == MetaRelation::MANY_TO_MANY) ) { // collections $primitiveName = 'identifierList'; + if( is_subclass_of($this->getType()->getClassName(), 'UuidIdentifiable') ) { + $primitiveName = 'uuidIdentifierList'; + } } elseif ($this->isIdentifier()) { if ($this->getType() instanceof IntegerType) { $primitiveName = 'integerIdentifier'; $className = $holder->getName(); + } elseif ($this->getType() instanceof UuidType) { + $primitiveName = 'uuidIdentifier'; + $className = $holder->getName(); } elseif ($this->getType() instanceof StringType) { $primitiveName = 'scalarIdentifier'; $className = $holder->getName(); @@ -342,33 +378,51 @@ public function toLightProperty(MetaClass $holder) if ($pattern instanceof EnumerationClassPattern) { $primitiveName = 'enumeration'; + } elseif($pattern instanceof EnumClassPattern) { + $primitiveName = 'enum'; + } elseif($pattern instanceof RegistryClassPattern) { + $primitiveName = 'registry'; } elseif ( $pattern instanceof DictionaryClassPattern && ($identifier = $this->getType()->getClass()->getIdentifier()) ) { if ($identifier->getType() instanceof IntegerType) { $primitiveName = 'integerIdentifier'; + } elseif ($identifier->getType() instanceof UuidType) { + $primitiveName = 'uuidIdentifier'; + } elseif ($identifier->getType() instanceof StringType) { + $primitiveName = 'scalarIdentifier'; + } else + $primitiveName = $this->getType()->getPrimitiveName(); + } elseif ( + $this->getType() instanceof ObjectType + && ($identifier = $this->getType()->getClass()->getIdentifier()) + ) { + if ($identifier->getType() instanceof IntegerType) { + $primitiveName = 'integerIdentifier'; + } elseif ($identifier->getType() instanceof UuidType) { + $primitiveName = 'uuidIdentifier'; } elseif ($identifier->getType() instanceof StringType) { $primitiveName = 'scalarIdentifier'; } else $primitiveName = $this->getType()->getPrimitiveName(); - } else + } else $primitiveName = $this->getType()->getPrimitiveName(); } else $primitiveName = $this->getType()->getPrimitiveName(); - + $inner = false; - + if ($this->getType() instanceof ObjectType) { $className = $this->getType()->getClassName(); - + if (!$this->getType()->isGeneric()) { $class = $this->getType()->getClass(); $pattern = $class->getPattern(); - + if ($pattern instanceof InternalClassPattern) $className = $holder->getName(); - + if ( ( ($pattern instanceof InternalClassPattern) @@ -381,13 +435,13 @@ public function toLightProperty(MetaClass $holder) } } } - + $propertyClassName = ( $inner ? 'InnerMetaProperty' : 'LightMetaProperty' ); - + if ( ($this->getType() instanceof IntegerType) ) { @@ -404,7 +458,7 @@ public function toLightProperty(MetaClass $holder) } else { $size = null; } - + return call_user_func_array( array($propertyClassName, 'fill'), @@ -430,14 +484,19 @@ private function buildColumn($columnName) { if (is_array($columnName)) { $out = array(); - + foreach ($columnName as $name) { $out[] = $this->buildColumn($name); } - + return $out; } - + + // hack for string ID length + if( $this->getType() instanceof ObjectType && !$this->getType()->isGeneric() ) { + $this->size = $this->getType()->getClass()->getIdentifier()->getSize(); + } + $column = <<size) { $column .= << setSize({$this->size}) EOT; } - + if ($this->type instanceof NumericType) { $column .= << setPrecision({$this->type->getPrecision()}) EOT; } - + $column .= << setPrimaryKey(true) EOT; - + if ($this->getType() instanceof IntegerType) { $column .= << @@ -484,10 +543,10 @@ private function buildColumn($columnName) EOT; } } - + if ($this->type->hasDefault()) { $default = $this->type->getDefault(); - + if ($this->type instanceof BooleanType) { if ($default) $default = 'true'; @@ -496,21 +555,21 @@ private function buildColumn($columnName) } elseif ($this->type instanceof StringType) { $default = "'{$default}'"; } - + $column .= << setDefault({$default}) EOT; } - + $column .= <<getOutput(); } - + /** + * @param $orly bool * @return MetaConfiguration **/ public function setForcedGeneration($orly) { $this->forcedGeneration = $orly; - + return $this; } - + public function isForcedGeneration() { return $this->forcedGeneration; } - + /** + * @param $dry bool * @return MetaConfiguration **/ public function setDryRun($dry) { $this->dryRun = $dry; - + return $this; } - + public function isDryRun() { return $this->dryRun; } - - /** - * @return MetaConfiguration - **/ - public function setWithEnumerationRefIntegrityCheck($orly) - { - $this->checkEnumerationRefIntegrity = $orly; - - return $this; - } - + + /** + * @return MetaConfigurationCorePlugin + */ + public function getCorePlugin() + { + if (!isset($this->corePlugin)) { + $this->corePlugin = new MetaConfigurationCorePlugin($this); + } + return $this->corePlugin; + } + + /** + * @param $name + * @return MetaConfigurationPluginInterface + * @throws MissingElementException + */ + public function getExternalPlugin($name) { + if (!isset($this->externalPlugins[$name])) { + throw new MissingElementException('no plugin for "<' . $name . '>"'); + } + return $this->externalPlugins[$name]; + } + + /** + * @return MetaConfigurationPluginInterface[] + */ + public function getPlugins() + { + return [ $this->getCorePlugin() ] + $this->externalPlugins; + } + + /** + * @param $metafile + * @param bool $generate * @return MetaConfiguration - **/ + * @throws MissingElementException + * @throws UnsupportedMethodException + * @throws WrongArgumentException + */ public function load($metafile, $generate = true) { $this->loadXml($metafile, $generate); - - // check sources - foreach ($this->classes as $name => $class) { - $sourceLink = $class->getSourceLink(); - if (isset($sourceLink)) { - Assert::isTrue( - isset($this->sources[$sourceLink]), - "unknown source '{$sourceLink}' specified " - ."for class '{$name}'" - ); - } elseif ($this->defaultSource) { - $class->setSourceLink($this->defaultSource); - } - } - - foreach ($this->liaisons as $class => $parent) { - if (isset($this->classes[$parent])) { - - Assert::isFalse( - $this->classes[$parent]->getTypeId() - == MetaClassType::CLASS_FINAL, - - "'{$parent}' is final, thus can not have childs" - ); - - if ( - $this->classes[$class]->getPattern() - instanceof DictionaryClassPattern - ) - throw new UnsupportedMethodException( - 'DictionaryClass pattern does ' - .'not support inheritance' - ); - - $this->classes[$class]->setParent( - $this->classes[$parent] - ); - } else - throw new MissingElementException( - "unknown parent class '{$parent}'" - ); - } - - // search for referencing classes - foreach ($this->references as $className => $list) { - $class = $this->getClassByName($className); - - if ( - ( - $class->getPattern() instanceof ValueObjectPattern - ) || ( - $class->getPattern() instanceof InternalClassPattern - ) || ( - $class->getPattern() instanceof AbstractClassPattern - ) - ) { - continue; - } - - foreach ($list as $refer) { - $remote = $this->getClassByName($refer); - if ( - ( - $remote->getPattern() instanceof ValueObjectPattern - ) && ( - isset($this->references[$refer]) - ) - ) { - foreach ($this->references[$refer] as $holder) { - $this->classes[$className]-> - setReferencingClass($holder); - } - } elseif ( - (!$remote->getPattern() instanceof AbstractClassPattern) - && (!$remote->getPattern() instanceof InternalClassPattern) - && ($remote->getTypeId() <> MetaClassType::CLASS_ABSTRACT) - ) { - $this->classes[$className]->setReferencingClass($refer); - } - } - } - - // final sanity checking - foreach ($this->classes as $name => $class) { - $this->checkSanity($class); - } - - // check for recursion in relations and spooked properties - foreach ($this->classes as $name => $class) { - foreach ($class->getProperties() as $property) { - if ($property->getRelationId() == MetaRelation::ONE_TO_ONE) { - if ( - ( - ( - $property->getType()->getClass()->getPattern() - instanceof SpookedClassPattern - ) || ( - $property->getType()->getClass()->getPattern() - instanceof SpookedEnumerationPattern - ) - ) && ( - $property->getFetchStrategy() - && ( - $property->getFetchStrategy()->getId() - != FetchStrategy::LAZY - ) - ) - ) { - $property->setFetchStrategy(FetchStrategy::cascade()); - } else { - $this->checkRecursion($property, $class); - } - } - } - } - - return $this; - } - - /** - * @return MetaConfiguration - **/ - public function buildClasses() - { - $out = $this->getOutput(); - - $out-> - infoLine('Building classes:'); - - foreach ($this->classes as $name => $class) { - if ( - !$class->doBuild() - || ($class->getPattern() instanceof InternalClassPattern) - ) { - continue; - } else { - $out->infoLine("\t".$name.':'); - } - - $class->dump(); - $out->newLine(); - } - - return $this; - } - - /** - * @return MetaConfiguration - **/ - public function buildSchema() - { - $out = $this->getOutput(); - $out-> - newLine()-> - infoLine('Building DB schema:'); - - $schema = SchemaBuilder::getHead(); - - $tables = array(); - - foreach ($this->classes as $class) { - if ( - (!$class->getParent() && !count($class->getProperties())) - || !$class->getPattern()->tableExists() - ) { - continue; - } - - foreach ($class->getAllProperties() as $property) - $tables[ - $class->getTableName() - ][ - // just to sort out dupes, if any - $property->getColumnName() - ] = $property; - } - - foreach ($tables as $name => $propertyList) - if ($propertyList) - $schema .= SchemaBuilder::buildTable($name, $propertyList); - - foreach ($this->classes as $class) { - if (!$class->getPattern()->tableExists()) { - continue; - } - - $schema .= SchemaBuilder::buildRelations($class); - } - - $schema .= '?>'; - - BasePattern::dumpFile( - ONPHP_META_AUTO_DIR.'schema.php', - Format::indentize($schema) - ); + foreach ($this->getPlugins() as $plugin) { + $plugin->checkConfig(); + } return $this; } - - /** - * @return MetaConfiguration - **/ - public function buildSchemaChanges() - { - $out = $this->getOutput(); - $out-> - newLine()-> - infoLine('Suggested DB-schema changes: '); - - require ONPHP_META_AUTO_DIR.'schema.php'; - - foreach ($this->classes as $class) { - if ( - $class->getTypeId() == MetaClassType::CLASS_ABSTRACT - || $class->getPattern() instanceof EnumerationClassPattern - ) - continue; - - try { - $target = $schema->getTableByName($class->getTableName()); - } catch (MissingElementException $e) { - // dropped or tableless - continue; - } - - try { - $db = DBPool::me()->getLink($class->getSourceLink()); - } catch (BaseException $e) { - $out-> - errorLine( - 'Can not connect using source link in \'' - .$class->getName().'\' class, skipping this step.' - ); - - break; - } - - try { - $source = $db->getTableInfo($class->getTableName()); - } catch (UnsupportedMethodException $e) { - $out-> - errorLine( - get_class($db) - .' does not support tables introspection yet.', - - true - ); - - break; - } catch (ObjectNotFoundException $e) { - $out->errorLine( - "table '{$class->getTableName()}' not found, skipping." - ); - continue; - } - - $diff = DBTable::findDifferences( - $db->getDialect(), - $source, - $target - ); - - if ($diff) { - foreach ($diff as $line) - $out->warningLine($line); - - $out->newLine(); - } - } - - return $this; - } - - /** - * @return MetaConfiguration - **/ - public function buildContainers() - { - $force = $this->isForcedGeneration(); - - $out = $this->getOutput(); - $out-> - infoLine('Building containers: '); - - foreach ($this->classes as $class) { - foreach ($class->getProperties() as $property) { - if ( - $property->getRelation() - && ($property->getRelationId() != MetaRelation::ONE_TO_ONE) - ) { - $userFile = - ONPHP_META_DAO_DIR - .$class->getName().ucfirst($property->getName()) - .'DAO' - .EXT_CLASS; - - if ($force || !file_exists($userFile)) { - BasePattern::dumpFile( - $userFile, - Format::indentize( - ContainerClassBuilder::buildContainer( - $class, - $property - ) - ) - ); - } - - // check for old-style naming - $oldStlye = - ONPHP_META_DAO_DIR - .$class->getName() - .'To' - .$property->getType()->getClassName() - .'DAO' - .EXT_CLASS; - - if (is_readable($oldStlye)) { - $out-> - newLine()-> - error( - 'remove manually: '.$oldStlye - ); - } - } - } - } - - return $this; - } - + + public function buildFiles() + { + foreach ($this->getPlugins() as $plugin) { + $plugin->buildFiles(); + } + } + /** * @return MetaConfiguration **/ public function checkIntegrity() { - $out = $this->getOutput()-> - newLine()-> - infoLine('Checking sanity of generated files: ')-> - newLine(); - - set_include_path( - get_include_path().PATH_SEPARATOR - .ONPHP_META_BUSINESS_DIR.PATH_SEPARATOR - .ONPHP_META_DAO_DIR.PATH_SEPARATOR - .ONPHP_META_PROTO_DIR.PATH_SEPARATOR - .ONPHP_META_AUTO_BUSINESS_DIR.PATH_SEPARATOR - .ONPHP_META_AUTO_DAO_DIR.PATH_SEPARATOR - .ONPHP_META_AUTO_PROTO_DIR.PATH_SEPARATOR - ); - - $out->info("\t"); - - $formErrors = array(); - - foreach ($this->classes as $name => $class) { - if ( - !( - $class->getPattern() instanceof SpookedClassPattern - || $class->getPattern() instanceof SpookedEnumerationPattern - || $class->getPattern() instanceof InternalClassPattern - ) && ( - class_exists($class->getName(), true) - ) - ) { - $out->info($name, true); - - $info = new ReflectionClass($name); - - $this-> - checkClassSanity($class, $info); - - if ($info->implementsInterface('Prototyped')) - $this->checkClassSanity( - $class, - new ReflectionClass('Proto'.$name) - ); - - if ($info->implementsInterface('DAOConnected')) - $this->checkClassSanity( - $class, - new ReflectionClass($name.'DAO') - ); - - foreach ($class->getInterfaces() as $interface) - Assert::isTrue( - $info->implementsInterface($interface), - - 'class '.$class->getName() - .' expected to implement interface '.$interface - ); - - // special handling for Enumeration instances - if ($class->getPattern() instanceof EnumerationClassPattern) { - $object = new $name(call_user_func(array($name, 'getAnyId'))); - - Assert::isTrue( - unserialize(serialize($object)) == $object - ); - - $out->info(', '); - - if ($this->checkEnumerationRefIntegrity) - $this->checkEnumerationReferentialIntegrity( - $object, - $class->getTableName() - ); - - continue; - } - - if ($class->getPattern() instanceof AbstractClassPattern) { - $out->info(', '); - continue; - } - - $object = new $name; - $proto = $object->proto(); - $form = $proto->makeForm(); - - foreach ($class->getProperties() as $name => $property) { - Assert::isTrue( - $property->toLightProperty($class) - == $proto->getPropertyByName($name), - - 'defined property does not match autogenerated one - ' - .$class->getName().'::'.$property->getName() - ); - } - - if (!$object instanceof DAOConnected) { - $out->info(', '); - continue; - } - - $dao = $object->dao(); - - Assert::isEqual( - $dao->getIdName(), - $class->getIdentifier()->getColumnName(), - 'identifier name mismatch in '.$class->getName().' class' - ); - - try { - DBPool::getByDao($dao); - } catch (MissingElementException $e) { - // skipping - $out->info(', '); - continue; - } - - $query = - Criteria::create($dao)-> - setLimit(1)-> - add(Expression::notNull($class->getIdentifier()->getName()))-> - addOrder($class->getIdentifier()->getName())-> - toSelectQuery(); - - $out->warning( - ' (' - .$query->getFieldsCount() - .'/' - .$query->getTablesCount() - .'/' - ); - - $clone = clone $object; - - if (serialize($clone) == serialize($object)) - $out->info('C', true); - else { - $out->error('C', true); - } - - $out->warning('/'); - - try { - $object = $dao->getByQuery($query); - $form = $object->proto()->makeForm(); - FormUtils::object2form($object, $form); - - if ($errors = $form->getErrors()) { - $formErrors[$class->getName()] = $errors; - - $out->error('F', true); - } else - $out->info('F', true); - } catch (ObjectNotFoundException $e) { - $out->warning('F'); - } - - $out->warning('/'); - - if ( - Criteria::create($dao)-> - setFetchStrategy(FetchStrategy::cascade())-> - toSelectQuery() - == $dao->makeSelectHead() - ) { - $out->info('H', true); - } else { - $out->error('H', true); - } - - $out->warning('/'); - - // cloning once again - $clone = clone $object; - - FormUtils::object2form($object, $form); - FormUtils::form2object($form, $object); - - if ($object != $clone) { - $out->error('T', true); - } else { - $out->info('T', true); - } - - $out->warning(')')->info(', '); - } - } - - $out->infoLine('done.'); - - if ($formErrors) { - $out->newLine()->errorLine('Errors found:')->newLine(); - - foreach ($formErrors as $class => $errors) { - $out->errorLine("\t".$class.':', true); - - foreach ($errors as $name => $error) { - $out->errorLine( - "\t\t".$name.' - ' - .( - $error == Form::WRONG - ? ' wrong' - : ' missing' - ) - ); - } - - $out->newLine(); - } - } - - return $this; + foreach ($this->getPlugins() as $plugin) { + $plugin->checkIntegrity(); + } } - + /** + * @param $drop bool * @return MetaConfiguration **/ public function checkForStaleFiles($drop = false) { - $this->getOutput()-> - newLine()-> - infoLine('Checking for stale files: '); - - return $this-> - checkDirectory(ONPHP_META_AUTO_BUSINESS_DIR, 'Auto', null, $drop)-> - checkDirectory(ONPHP_META_AUTO_DAO_DIR, 'Auto', 'DAO', $drop)-> - checkDirectory(ONPHP_META_AUTO_PROTO_DIR, 'AutoProto', null, $drop); - } - - /** - * @throws MissingElementException - * @return MetaClass - **/ - public function getClassByName($name) - { - if (isset($this->classes[$name])) - return $this->classes[$name]; - - throw new MissingElementException( - "knows nothing about '{$name}' class" - ); - } - - public function getClassList() - { - return $this->classes; + foreach ($this->getPlugins() as $plugin) { + $plugin->checkForStaleFiles($drop); + } } - + /** + * @param $out MetaOutput * @return MetaConfiguration **/ public function setOutput(MetaOutput $out) { $this->out = $out; - + return $this; } - + /** * @return MetaOutput **/ @@ -691,270 +171,10 @@ public function getOutput() { return $this->out; } - - /** - * @return MetaConfiguration - **/ - private function checkDirectory( - $directory, $preStrip, $postStrip, $drop = false - ) - { - $out = $this->getOutput(); - - foreach ( - glob($directory.'*.class.php', GLOB_NOSORT) - as $filename - ) { - $name = - substr( - basename($filename, $postStrip.EXT_CLASS), - strlen($preStrip) - ); - - if (!isset($this->classes[$name])) { - $out->warning( - "\t" - .str_replace( - getcwd().DIRECTORY_SEPARATOR, - null, - $filename - ) - ); - - if ($drop) { - try { - unlink($filename); - $out->infoLine(' removed.'); - } catch (BaseException $e) { - $out->errorLine(' failed to remove.'); - } - } else { - $out->newLine(); - } - } - } - - return $this; - } - - /** - * @return MetaConfiguration - **/ - private function addSource(SimpleXMLElement $source) - { - $name = (string) $source['name']; - - $default = - isset($source['default']) && (string) $source['default'] == 'true' - ? true - : false; - - Assert::isFalse( - isset($this->sources[$name]), - "duplicate source - '{$name}'" - ); - - Assert::isFalse( - $default && $this->defaultSource !== null, - 'too many default sources' - ); - - $this->sources[$name] = $default; - - if ($default) - $this->defaultSource = $name; - - return $this; - } - - /** - * @return MetaClassProperty - **/ - private function makeProperty($name, $type, MetaClass $class, $size) - { - Assert::isFalse( - strpos($name, '_'), - 'naming convention violation spotted' - ); - - if (!$name || !$type) - throw new WrongArgumentException( - 'strange name or type given: "'.$name.'" - "'.$type.'"' - ); - - if (is_readable(ONPHP_META_TYPES.$type.'Type'.EXT_CLASS)) - $typeClass = $type.'Type'; - else - $typeClass = 'ObjectType'; - - $property = new MetaClassProperty($name, new $typeClass($type), $class); - - if ($size) - $property->setSize($size); - else { - Assert::isTrue( - ( - !$property->getType() - instanceof FixedLengthStringType - ) && ( - !$property->getType() - instanceof NumericType - ) && ( - !$property->getType() - instanceof HttpUrlType - ), - - 'size is required for "'.$property->getName().'"' - ); - } - - return $property; - } - - /** - * @throws MissingElementException - * @return GenerationPattern - **/ - private function guessPattern($name) - { - $class = $name.'Pattern'; - - if (is_readable(ONPHP_META_PATTERNS.$class.EXT_CLASS)) - return Singleton::getInstance($class); - - throw new MissingElementException( - "unknown pattern '{$name}'" - ); - } - - /** - * @return MetaConfiguration - **/ - private function checkSanity(MetaClass $class) - { - if ( - ( - !$class->getParent() - || ( - $class->getFinalParent()->getPattern() - instanceof InternalClassPattern - ) - ) - && (!$class->getPattern() instanceof ValueObjectPattern) - && (!$class->getPattern() instanceof InternalClassPattern) - ) { - Assert::isTrue( - $class->getIdentifier() !== null, - 'only value objects can live without identifiers. ' - .'do not use them anyway (' - .$class->getName().')' - ); - } - - if ( - $class->getType() - && $class->getTypeId() - == MetaClassType::CLASS_SPOOKED - ) { - Assert::isFalse( - count($class->getProperties()) > 1, - 'spooked classes must have only identifier: ' - .$class->getName() - ); - - Assert::isTrue( - ($class->getPattern() instanceof SpookedClassPattern - || $class->getPattern() instanceof SpookedEnumerationPattern), - 'spooked classes must use spooked patterns only: ' - .$class->getName() - ); - } - - foreach ($class->getProperties() as $property) { - if ( - !$property->getType()->isGeneric() - && $property->getType() instanceof ObjectType - && - $property->getType()->getClass()->getPattern() - instanceof ValueObjectPattern - ) { - Assert::isTrue( - $property->isRequired(), - 'optional value object is not supported:' - .$property->getName().' @ '.$class->getName() - ); - - Assert::isTrue( - $property->getRelationId() == MetaRelation::ONE_TO_ONE, - 'value objects must have OneToOne relation: ' - .$property->getName().' @ '.$class->getName() - - ); - } elseif ( - ($property->getFetchStrategyId() == FetchStrategy::LAZY) - && $property->getType()->isGeneric() - ) { - throw new WrongArgumentException( - 'lazy one-to-one is supported only for ' - .'non-generic object types ' - .'('.$property->getName() - .' @ '.$class->getName() - .')' - ); - } - } - - return $this; - } - - private function checkRecursion( - MetaClassProperty $property, - MetaClass $holder, - $paths = array() - ) { - Assert::isTrue( - $property->getRelationId() - == MetaRelation::ONE_TO_ONE - ); - - if ( - $property->getFetchStrategy() - && $property->getFetchStrategy()->getId() != FetchStrategy::JOIN - ) { - return false; - } - $remote = $property->getType()->getClass(); - - if (isset($paths[$holder->getName()][$remote->getName()])) - return true; - else { - $paths[$holder->getName()][$remote->getName()] = true; - - foreach ($remote->getProperties() as $remoteProperty) { - if ( - $remoteProperty->getRelationId() - == MetaRelation::ONE_TO_ONE - ) { - if ( - $this->checkRecursion( - $remoteProperty, - $holder, - $paths - ) - ) { - $remoteProperty->setFetchStrategy( - FetchStrategy::cascade() - ); - } - } - } - } - - return false; - } - /** + * @param SimpleXMLElement $xml + * @param string $metafile * @return MetaConfiguration **/ private function processIncludes(SimpleXMLElement $xml, $metafile) @@ -962,277 +182,68 @@ private function processIncludes(SimpleXMLElement $xml, $metafile) foreach ($xml->include as $include) { $file = (string) $include['file']; $path = dirname($metafile).'/'.$file; - + Assert::isTrue( is_readable($path), 'can not include '.$file ); - + $this->getOutput()-> infoLine('Including "'.$path.'".')-> newLine(); - + $this->loadXml($path, !((string) $include['generate'] == 'false')); } - + return $this; } - + /** + * @param SimpleXMLElement $xml * @return MetaConfiguration **/ - private function processClasses(SimpleXMLElement $xml, $metafile, $generate) + private function processPlugins(SimpleXMLElement $xml) { - foreach ($xml->classes[0] as $xmlClass) { - $name = (string) $xmlClass['name']; - - Assert::isFalse( - isset($this->classes[$name]), - 'class name collision found for '.$name - ); - - $class = new MetaClass($name); - - if (isset($xmlClass['source'])) - $class->setSourceLink((string) $xmlClass['source']); - - if (isset($xmlClass['table'])) - $class->setTableName((string) $xmlClass['table']); - - if (isset($xmlClass['type'])) { - $type = (string) $xmlClass['type']; - - if ($type == 'spooked') { - $this->getOutput()-> - warning($class->getName(), true)-> - warningLine(': uses obsoleted "spooked" type.')-> - newLine(); - } - - $class->setType( - new MetaClassType( - (string) $xmlClass['type'] - ) - ); - } - - // lazy existence checking - if (isset($xmlClass['extends'])) - $this->liaisons[$class->getName()] = (string) $xmlClass['extends']; - - // populate implemented interfaces - foreach ($xmlClass->implement as $xmlImplement) - $class->addInterface((string) $xmlImplement['interface']); - - if (isset($xmlClass->properties[0]->identifier)) { - - $id = $xmlClass->properties[0]->identifier; - - if (!isset($id['name'])) - $name = 'id'; - else - $name = (string) $id['name']; - - if (!isset($id['type'])) - $type = 'BigInteger'; - else - $type = (string) $id['type']; - - $property = $this->makeProperty( - $name, - $type, - $class, - // not casting to int because of Numeric possible size - (string) $id['size'] - ); - - if (isset($id['column'])) { - $property->setColumnName( - (string) $id['column'] - ); - } elseif ( - $property->getType() instanceof ObjectType - && !$property->getType()->isGeneric() - ) { - $property->setColumnName($property->getConvertedName().'_id'); - } else { - $property->setColumnName($property->getConvertedName()); - } - - $property-> - setIdentifier(true)-> - required(); - - $class->addProperty($property); - - unset($xmlClass->properties[0]->identifier); - } - - $class->setPattern( - $this->guessPattern((string) $xmlClass->pattern['name']) - ); - - if ((string) $xmlClass->pattern['fetch'] == 'cascade') - $class->setFetchStrategy(FetchStrategy::cascade()); - - if ($class->getPattern() instanceof InternalClassPattern) { - Assert::isTrue( - $metafile === ONPHP_META_PATH.'internal.xml', - 'internal classes can be defined only in onPHP, sorry' - ); - } elseif ( - ( - $class->getPattern() instanceof SpookedClassPattern - ) || ( - $class->getPattern() instanceof SpookedEnumerationPattern - ) - ) { - $class->setType( - new MetaClassType( - MetaClassType::CLASS_SPOOKED - ) - ); - } - - // populate properties - foreach ($xmlClass->properties[0] as $xmlProperty) { - - $property = $this->makeProperty( - (string) $xmlProperty['name'], - (string) $xmlProperty['type'], - $class, - (string) $xmlProperty['size'] - ); - - if (isset($xmlProperty['column'])) { - $property->setColumnName( - (string) $xmlProperty['column'] - ); - } elseif ( - $property->getType() instanceof ObjectType - && !$property->getType()->isGeneric() - ) { - if ( - isset( - $this->classes[ - $property->getType()->getClassName() - ] - ) && ( - $property->getType()->getClass()->getPattern() - instanceof InternalClassPattern - ) - ) { - throw new UnimplementedFeatureException( - 'you can not use internal classes directly atm' - ); - } - - $property->setColumnName($property->getConvertedName().'_id'); - } else { - $property->setColumnName($property->getConvertedName()); - } - - if ((string) $xmlProperty['required'] == 'true') - $property->required(); - - if (isset($xmlProperty['identifier'])) { - throw new WrongArgumentException( - 'obsoleted identifier description found in ' - ."{$class->getName()} class;\n" - .'you must use instead.' - ); - } - - if (!$property->getType()->isGeneric()) { - - if (!isset($xmlProperty['relation'])) - throw new MissingElementException( - 'relation should be set for non-generic ' - ."property '{$property->getName()}' type '" - .get_class($property->getType())."'" - ." of '{$class->getName()}' class" - ); - else { - $property->setRelation( - MetaRelation::makeFromName( - (string) $xmlProperty['relation'] - ) - ); - - if ($fetch = (string) $xmlProperty['fetch']) { - Assert::isTrue( - $property->getRelationId() - == MetaRelation::ONE_TO_ONE, - - 'fetch mode can be specified - only for OneToOne relations' - ); - - if ($fetch == 'lazy') - $property->setFetchStrategy( - FetchStrategy::lazy() - ); - elseif ($fetch == 'cascade') - $property->setFetchStrategy( - FetchStrategy::cascade() - ); - else - throw new WrongArgumentException( - 'strange fetch mode found - '.$fetch - ); - } - - if ( - ( - ( - $property->getRelationId() - == MetaRelation::ONE_TO_ONE - ) && ( - $property->getFetchStrategyId() - != FetchStrategy::LAZY - ) - ) && ( - $property->getType()->getClassName() - <> $class->getName() - ) - ) { - $this->references[$property->getType()->getClassName()][] - = $class->getName(); - } - } - } - - if (isset($xmlProperty['default'])) { - // will be correctly autocasted further down the code - $property->getType()->setDefault( - (string) $xmlProperty['default'] - ); - } - - $class->addProperty($property); - } - - $class->setBuild($generate); - - $this->classes[$class->getName()] = $class; + foreach ($xml->plugin as $plugin) { + $class = (string) $plugin['class']; + $namespace = (string) $plugin['name']; + Assert::classExists($class, + 'could not load plugin class ' . $class + ); + Assert::isInstance($class, MetaConfigurationPluginInterface::class, + $class . ' is not a meta configuration plugin' + ); + Assert::isFalse(isset($this->externalPlugins[$namespace]), + 'another plugin is already defined for <' . $namespace . '>' + ); + + /** @var MetaConfigurationPluginInterface $plugin */ + $plugin = new $class($this); + + $this->externalPlugins[$namespace] = $plugin; + + $this->getOutput() + ->infoLine('Using plugin "' . $class . '" for <' . $namespace . '>.') + ->newLine(); } - + return $this; } - + private function loadXml($metafile, $generate) { $contents = file_get_contents($metafile); - - $contents = str_replace( - '"meta.dtd"', - '"'.ONPHP_META_PATH.'dtd/meta.dtd"', - $contents - ); - + + foreach ($this->getPlugins() as $plugin) { + // to validate XML properly, we need to specify full path to DTD file instead of just name + foreach ($plugin->getDtdMapping() as $dtdFilename => $dtdFullFilePath) { + $contents = str_replace('"' . $dtdFilename . '"', '"' . $dtdFullFilePath . '"', $contents); + } + } + $doc = new DOMDocument('1.0'); $doc->loadXML($contents); - + try { $doc->validate(); } catch (BaseException $e) { @@ -1242,125 +253,29 @@ private function loadXml($metafile, $generate) .$error->line.' in file '.$metafile ); } - + $xml = simplexml_import_dom($doc); - - // populate sources (if any) - if (isset($xml->sources[0])) { - foreach ($xml->sources[0] as $source) { - $this->addSource($source); - } - } - - if (isset($xml->include['file'])) { - $this->processIncludes($xml, $metafile); - } - - // otherwise it's an includes-only config - if (isset($xml->classes[0])) { - return $this->processClasses($xml, $metafile, $generate); - } - - return $this; - } - - /** - * @return MetaConfiguration - **/ - private function checkClassSanity( - MetaClass $class, - ReflectionClass $info - ) - { - switch ($class->getTypeId()) { - case null: - break; - - case MetaClassType::CLASS_ABSTRACT: - Assert::isTrue( - $info->isAbstract(), - 'class '.$info->getName().' expected to be abstract' - ); - Assert::isTrue( - $class->getPattern() instanceof AbstractClassPattern, - 'class '.$info->getName().' must use AbstractClassPattern' - ); - break; - - case MetaClassType::CLASS_FINAL: - Assert::isTrue( - $info->isFinal(), - 'class '.$info->getName().' expected to be final' - ); - break; - - case MetaClassType::CLASS_SPOOKED: - default: - Assert::isUnreachable(); - break; - } - - if ($public = $info->getProperties(ReflectionProperty::IS_PUBLIC)) { - Assert::isUnreachable( - $class->getName() - .' contains properties with evil visibility:' - ."\n" - .print_r($public, true) - ); - } - - return $this; - } - - private function checkEnumerationReferentialIntegrity( - Enumeration $enumeration, $tableName - ) - { - $updateQueries = null; - - $db = DBPool::me()->getLink(); - - $class = get_class($enumeration); - - $ids = array(); - - $list = $enumeration->getObjectList(); - - foreach ($list as $enumerationObject) - $ids[$enumerationObject->getId()] = $enumerationObject->getName(); - - $rows = - $db->querySet( - OSQL::select()->from($tableName)-> - multiGet('id', 'name') - ); - - echo "\n"; - - foreach ($rows as $row) { - if (!isset($ids[$row['id']])) - echo "Class '{$class}', strange id: {$row['id']} found. \n"; - else { - if ($ids[$row['id']] != $row['name']) { - echo "Class '{$class}',id: {$row['id']} sync names. \n"; - - $updateQueries .= - OSQL::update($tableName)-> - set('name', $ids[$row['id']])-> - where(Expression::eq('id', $row['id']))-> - toDialectString($db->getDialect()) . ";\n"; - } - - unset($ids[$row['id']]); - } - } - - foreach ($ids as $id => $name) - echo "Class '{$class}', id: {$id} not present in database. \n"; - - echo $updateQueries; - + + $rootName = $xml->getName(); + + if ($rootName === 'metaconfiguration') { + if (isset($xml->plugin)) { + $this->processPlugins($xml); + } + + if (isset($xml->include)) { + $this->processIncludes($xml, $metafile); + } + + $plugin = $this->getCorePlugin(); + + } else { + $plugin = $this->getExternalPlugin($rootName); + } + + $plugin->loadConfig($xml, $metafile, $generate); + return $this; } + } -?> \ No newline at end of file diff --git a/meta/classes/MetaConfigurationCorePlugin.class.php b/meta/classes/MetaConfigurationCorePlugin.class.php new file mode 100644 index 0000000000..5c0595baa2 --- /dev/null +++ b/meta/classes/MetaConfigurationCorePlugin.class.php @@ -0,0 +1,1503 @@ + + * @date 2015-11-11 + */ +class MetaConfigurationCorePlugin implements MetaConfigurationPluginInterface +{ + /** @var MetaConfiguration */ + protected $meta; + + /** @var MetaClass[] */ + protected $classes = array(); + /** @var bool[] (string)name => (bool)isDefault */ + protected $sources = array(); + /** @var null|string */ + protected $defaultSource = null; + /** string[] (string)className => (string)parentClassName */ + protected $liaisons = array(); + /** @var string[][] (string)className => (string[])propertyClassNames */ + protected $references = array(); + /** @var bool */ + protected $checkEnumerationRefIntegrity = false; + /** @var bool */ + protected $buildClasses = true; + /** @var bool */ + protected $buildContainers = true; + /** @var bool */ + protected $buildSchema = true; + /** @var bool */ + protected $buildSchemaChanges = true; + + /** + * @return string[] (string)dtd-name => (string)dtd-filepath + */ + public function getDtdMapping() + { + return [ + 'meta.dtd' => ONPHP_META_PATH . 'dtd/meta.dtd' + ]; + } + + /** + * MetaConfigurationCorePlugin constructor. + * @param MetaConfiguration $metaConfiguration + */ + public function __construct(MetaConfiguration $metaConfiguration) + { + $this->meta = $metaConfiguration; + } + + /** + * @param SimpleXMLElement $xml + * @param $metafile + * @param $generate + * @return void + * @throws MissingElementException + * @throws UnimplementedFeatureException + * @throws WrongArgumentException + */ + public function loadConfig(SimpleXMLElement $xml, $metafile, $generate) + { + // populate sources (if any) + if (isset($xml->sources[0])) { + foreach ($xml->sources[0] as $source) { + $this->addSource($source); + } + } + + if (isset($xml->classes[0])) { + $this->processClasses($xml, $metafile, $generate); + } + } + + /** + * @throws MissingElementException + * @throws UnsupportedMethodException + * @throws WrongArgumentException + */ + public function checkConfig() + { + // check sources + foreach ($this->classes as $name => $class) { + $sourceLink = $class->getSourceLink(); + if (isset($sourceLink)) { + Assert::isTrue( + isset($this->sources[$sourceLink]), + "unknown source '{$sourceLink}' specified " + . "for class '{$name}'" + ); + } elseif ($this->defaultSource) { + $class->setSourceLink($this->defaultSource); + } + } + + foreach ($this->liaisons as $class => $parent) { + if (isset($this->classes[$parent])) { + + Assert::isFalse( + $this->classes[$parent]->getTypeId() + == MetaClassType::CLASS_FINAL, + + "'{$parent}' is final, thus can not have childs" + ); + + if ( + $this->classes[$class]->getPattern() + instanceof DictionaryClassPattern + ) + throw new UnsupportedMethodException( + 'DictionaryClass pattern does ' + . 'not support inheritance' + ); + + $this->classes[$class]->setParent( + $this->classes[$parent] + ); + } else + throw new MissingElementException( + "unknown parent class '{$parent}'" + ); + } + + // search for referencing classes + foreach ($this->references as $className => $list) { + $class = $this->getClassByName($className); + + if ( + ( + $class->getPattern() instanceof ValueObjectPattern + ) || ( + $class->getPattern() instanceof InternalClassPattern + ) || ( + $class->getPattern() instanceof AbstractClassPattern + ) + ) { + continue; + } + + foreach ($list as $refer) { + $remote = $this->getClassByName($refer); + if ( + ( + $remote->getPattern() instanceof ValueObjectPattern + ) && ( + isset($this->references[$refer]) + ) + ) { + foreach ($this->references[$refer] as $holder) { + $this->classes[$className] + ->setReferencingClass($holder); + } + } elseif ( + (!$remote->getPattern() instanceof AbstractClassPattern) + && (!$remote->getPattern() instanceof InternalClassPattern) + && ($remote->getTypeId() <> MetaClassType::CLASS_ABSTRACT) + ) { + $this->classes[$className]->setReferencingClass($refer); + } + } + } + + // final sanity checking + foreach ($this->classes as $name => $class) { + $this->checkSanity($class); + } + + // check for recursion in relations and spooked properties + foreach ($this->classes as $name => $class) { + foreach ($class->getProperties() as $property) { + if ($property->getRelationId() == MetaRelation::ONE_TO_ONE) { + if ( + ( + ( + $property->getType()->getClass()->getPattern() + instanceof SpookedClassPattern + ) || ( + $property->getType()->getClass()->getPattern() + instanceof SpookedEnumerationPattern + ) || ( + $property->getType()->getClass()->getPattern() + instanceof SpookedEnumPattern + ) || ( + $property->getType()->getClass()->getPattern() + instanceof SpookedRegistryPattern + ) + ) && ( + $property->getFetchStrategy() + && ( + $property->getFetchStrategy()->getId() + != FetchStrategy::LAZY + ) + ) + ) { + $property->setFetchStrategy(FetchStrategy::cascade()); + } else { + $this->checkRecursion($property, $class); + } + } + } + } + } + + public function buildFiles() + { + if ($this->isBuildClasses()) { + $this->buildClasses(); + } + + if ($this->isBuildContainers()) { + $this->buildContainers(); + } + + if ($this->isBuildSchema()) { + $this->buildSchema(); + } + + if ($this->isBuildSchemaChanges()) { + $this->buildSchemaChanges(); + } + } + + public function checkIntegrity() + { + $out = $this->getOutput(); + $out + ->newLine() + ->infoLine('Checking sanity of generated files: ') + ->newLine(); + + set_include_path( + get_include_path() . PATH_SEPARATOR + . ONPHP_META_BUSINESS_DIR . PATH_SEPARATOR + . ONPHP_META_DAO_DIR . PATH_SEPARATOR + . ONPHP_META_PROTO_DIR . PATH_SEPARATOR + . ONPHP_META_AUTO_BUSINESS_DIR . PATH_SEPARATOR + . ONPHP_META_AUTO_DAO_DIR . PATH_SEPARATOR + . ONPHP_META_AUTO_PROTO_DIR . PATH_SEPARATOR + ); + + $out->info("\t"); + + $formErrors = array(); + + foreach ($this->classes as $name => $class) { + /** @var $class MetaClass */ + if ( + !( + $class->getPattern() instanceof SpookedClassPattern + || $class->getPattern() instanceof SpookedEnumerationPattern + || $class->getPattern() instanceof SpookedEnumPattern + || $class->getPattern() instanceof SpookedRegistryPattern + || $class->getPattern() instanceof InternalClassPattern + ) && ( + class_exists($class->getName(), true) + ) + ) { + $out->info($name, true); + + $info = new ReflectionClass($name); + + $this->checkClassSanity($class, $info); + + if ($info->implementsInterface('Prototyped')) + $this->checkClassSanity( + $class, + new ReflectionClass('Proto' . $name) + ); + + if ($info->implementsInterface('DAOConnected')) + $this->checkClassSanity( + $class, + new ReflectionClass($name . 'DAO') + ); + + foreach ($class->getInterfaces() as $interface) + Assert::isTrue( + $info->implementsInterface($interface), + + 'class ' . $class->getName() + . ' expected to implement interface ' . $interface + ); + + // special handling for Enumeration instances + if ( + $class->getPattern() instanceof EnumerationClassPattern + || $class->getPattern() instanceof EnumClassPattern + || $class->getPattern() instanceof RegistryClassPattern + ) { + $object = new $name(call_user_func(array($name, 'getAnyId'))); + + Assert::isTrue( + unserialize(serialize($object)) == $object + ); + + $out->info(', '); + + if ($this->checkEnumerationRefIntegrity) { + if ( + $object instanceof Enumeration + || $object instanceof Enum + || $object instanceof RegistryClassPattern + ) + $this->checkEnumerationReferentialIntegrity( + $object, + $class->getTableName() + ); + } + + + continue; + } + + if ($class->getPattern() instanceof AbstractClassPattern) { + $out->info(', '); + continue; + } + + /** @var Prototyped|DAOConnected $object */ + $object = new $name; + $proto = $object->proto(); + $form = $proto->makeForm(); + + foreach ($class->getProperties() as $property) { + Assert::isTrue( + $property->toLightProperty($class) + == $proto->getPropertyByName($property->getName()), + + 'defined property does not match autogenerated one - ' + . $class->getName() . '::' . $property->getName() + ); + } + + if (!$object instanceof DAOConnected) { + $out->info(', '); + continue; + } + + $dao = $object->dao(); + + if (!$dao instanceof GenericDAO) { + $out->info(', '); + continue; + } + + Assert::isEqual( + $dao->getIdName(), + $class->getIdentifier()->getColumnName(), + 'identifier name mismatch in ' . $class->getName() . ' class' + ); + + if ($class->getPattern() instanceof NoSqlClassPattern) { + try { + NoSqlPool::getByDao($dao); + } catch (MissingElementException $e) { + // skipping + $out->info(', '); + continue; + } + + $out->info(', '); + } else { + try { + DBPool::getByDao($dao); + } catch (MissingElementException $e) { + // skipping + $out->info(', '); + continue; + } + + $query = + Criteria::create($dao) + ->setLimit(1) + ->add(Expression::notNull($class->getIdentifier()->getName())) + ->addOrder($class->getIdentifier()->getName()) + ->toSelectQuery(); + + $out->warning( + ' (' + . $query->getFieldsCount() + . '/' + . $query->getTablesCount() + . '/' + ); + + $clone = clone $object; + + if (serialize($clone) == serialize($object)) + $out->info('C', true); + else { + $out->error('C', true); + } + + $out->warning('/'); + + try { + $object = $dao->getByQuery($query); + $form = $object->proto()->makeForm(); + FormUtils::object2form($object, $form); + + if ($errors = $form->getErrors()) { + $formErrors[$class->getName()] = $errors; + + $out->error('F', true); + } else + $out->info('F', true); + } catch (ObjectNotFoundException $e) { + $out->warning('F'); + } + + $out->warning('/'); + + if ( + Criteria::create($dao) + ->setFetchStrategy(FetchStrategy::cascade()) + ->toSelectQuery() + == $dao->makeSelectHead() + ) { + $out->info('H', true); + } else { + $out->error('H', true); + } + + $out->warning('/'); + + // cloning once again + $clone = clone $object; + + try { + FormUtils::object2form($object, $form); + FormUtils::form2object($form, $object); + } catch (ObjectNotFoundException $e) { + $clone = null; + $out->error('(' . $e->getMessage() . ')'); + } + + if ($object != $clone) { + $out->error('T', true); + } else { + $out->info('T', true); + } + + $out->warning(')')->info(', '); + } + + } + } + + $out->infoLine('done.'); + + if ($formErrors) { + $out->newLine()->errorLine('Errors found:')->newLine(); + + foreach ($formErrors as $className => $errors) { + $out->errorLine("\t" . $className . ':', true); + + foreach ($errors as $propertyName => $error) { + $out->errorLine( + "\t\t$propertyName - " + . ($error == Form::WRONG ? 'wrong' : 'missing') + ); + } + + $out->newLine(); + } + } + + return $this; + } + + /** + * @param bool $drop + * @return $this + */ + public function checkForStaleFiles($drop = false) + { + $this->getOutput() + ->newLine() + ->infoLine('Checking for stale files: '); + + return $this + ->checkDirectory(ONPHP_META_AUTO_BUSINESS_DIR, 'Auto', null, $drop) + ->checkDirectory(ONPHP_META_AUTO_DAO_DIR, 'Auto', 'DAO', $drop) + ->checkDirectory(ONPHP_META_AUTO_PROTO_DIR, 'AutoProto', null, $drop); + } + + /** + * @return boolean + */ + public function isBuildClasses() + { + return $this->buildClasses; + } + + /** + * @param boolean $buildClasses + * @return $this + */ + public function setBuildClasses($buildClasses) + { + $this->buildClasses = $buildClasses; + return $this; + } + + /** + * @return boolean + */ + public function isBuildContainers() + { + return $this->buildContainers; + } + + /** + * @param boolean $buildContainers + * @return MetaConfigurationCorePlugin + */ + public function setBuildContainers($buildContainers) + { + $this->buildContainers = $buildContainers; + return $this; + } + + /** + * @return boolean + */ + public function isBuildSchema() + { + return $this->buildSchema; + } + + /** + * @param boolean $buildSchema + * @return MetaConfigurationCorePlugin + */ + public function setBuildSchema($buildSchema) + { + $this->buildSchema = $buildSchema; + return $this; + } + + /** + * @return boolean + */ + public function isBuildSchemaChanges() + { + return $this->buildSchemaChanges; + } + + /** + * @param boolean $buildSchemaChanges + * @return MetaConfigurationCorePlugin + */ + public function setBuildSchemaChanges($buildSchemaChanges) + { + $this->buildSchemaChanges = $buildSchemaChanges; + return $this; + } + + public function buildClasses() + { + $out = $this->getOutput(); + + $out->infoLine('Building classes:'); + + foreach ($this->classes as $name => $class) { + if ( + !$class->doBuild() + || ($class->getPattern() instanceof InternalClassPattern) + ) { + continue; + } else { + $out->infoLine("\t" . $name . ':'); + } + + $class->dump(); + $out->newLine(); + } + + return $this; + } + + public function buildSchema() + { + $out = $this->getOutput(); + $out->newLine()->infoLine('Building DB schema:'); + + $schema = SchemaBuilder::getHead(); + + $tables = array(); + + foreach ($this->classes as $class) { + if ( + (!$class->getParent() && !count($class->getProperties())) + || !$class->getPattern()->tableExists() + ) { + continue; + } + + foreach ($class->getAllProperties() as $property) + $tables[$class->getTableName()][// just to sort out dupes, if any + $property->getColumnName()] = $property; + } + + foreach ($tables as $name => $propertyList) + if ($propertyList) + $schema .= SchemaBuilder::buildTable($name, $propertyList); + + foreach ($this->classes as $class) { + if (!$class->getPattern()->tableExists()) { + continue; + } + + $schema .= SchemaBuilder::buildRelations($class); + } + + $schema .= '?>'; + + BasePattern::dumpFile( + ONPHP_META_AUTO_DIR . 'schema.php', + Format::indentize($schema) + ); + + return $this; + } + + public function buildSchemaChanges() + { + $out = $this->getOutput(); + $out + ->newLine() + ->infoLine('Suggested DB-schema changes: '); + + /** @noinspection PhpIncludeInspection */ + require ONPHP_META_AUTO_DIR . 'schema.php'; + if (!isset($schema) || !($schema instanceof DBSchema)) { + $out->errorLine('Could not import schema from schema.php'); + return $this; + } + + /** @var $class MetaClass */ + foreach ($this->classes as $class) { + if ( + $class->getTypeId() == MetaClassType::CLASS_ABSTRACT + || $class->getPattern() instanceof EnumerationClassPattern + || $class->getPattern() instanceof EnumClassPattern + || $class->getPattern() instanceof RegistryClassPattern + ) + continue; + + try { + $target = $schema->getTableByName($class->getTableName()); + } catch (MissingElementException $e) { + // dropped or tableless + continue; + } + + if ($class->getPattern() instanceof NoSqlClassPattern) { + // checking NoSQL-DB + try { + NoSqlPool::me()->getLink($class->getSourceLink()); + } catch (Exception $e) { + $out->errorLine( + 'Can not connect using source link in \'' + . $class->getName() . '\' class, skipping this step.' + ); + + break; + } + } else { + // checking SQL-DB + try { + $db = DBPool::me()->getLink($class->getSourceLink()); + } catch (Exception $e) { + $out->errorLine( + 'Can not connect using source link in \'' + . $class->getName() . '\' class, skipping this step.' + ); + + break; + } + + try { + $source = $db->getTableInfo($class->getTableName()); + } catch (UnsupportedMethodException $e) { + $out->errorLine( + get_class($db) + . ' does not support tables introspection yet.', + + true + ); + + break; + } catch (ObjectNotFoundException $e) { + $out->remarkLine( + $target->toDialectString($db->getDialect()), + ConsoleMode::FG_BLUE, + true + ); + + continue; + } + + $diff = DBTable::findDifferences( + $db->getDialect(), + $source, + $target + ); + + if ($diff) { + foreach ($diff as $line) + $out->warningLine($line); + + $out->newLine(); + } + + $className = $class->getName(); + /** @var $property MetaClassProperty */ + foreach ($class->getProperties() as $property) { + if ($property->getRelationId() == MetaRelation::MANY_TO_MANY) { + try { + /** @var ManyToManyLinked $manyToManyClass */ + $manyToManyClass = $property->toLightProperty($class)->getContainerName($className); + + if (is_subclass_of($manyToManyClass, 'ManyToManyLinked')) { + /** @var ManyToManyLinked $manyToManyObject */ + $manyToManyObject = new $manyToManyClass(new $className); + + $target = $schema->getTableByName($manyToManyObject->getHelperTable()); + + // may throw ObjectNotFoundException + $db->getTableInfo($manyToManyObject->getHelperTable()); + } + } catch (ObjectNotFoundException $e) { + $out->remarkLine( + $target->toDialectString($db->getDialect()), + ConsoleMode::FG_MAGENTA, + true + ); + } + } + } + } + + } + + return $this; + } + + public function buildContainers() + { + $force = $this->meta->isForcedGeneration(); + + $out = $this->getOutput(); + $out + ->infoLine('Building containers: ') + ->newLine(); + + foreach ($this->classes as $class) { + foreach ($class->getProperties() as $property) { + if ( + $property->getRelation() + && ($property->getRelationId() != MetaRelation::ONE_TO_ONE) + ) { + $className = $class->getName() . ucfirst($property->getName()) . 'DAO'; + $fileName = ONPHP_META_DAO_DIR . $className . EXT_CLASS; + + if ($force || !file_exists($fileName)) { + BasePattern::dumpFile( + $fileName, + Format::indentize( + ContainerClassBuilder::buildContainer( + $class, + $property + ) + ) + ); + } + + // check for old-style naming + $oldStyle = + ONPHP_META_DAO_DIR + . $class->getName() + . 'To' + . $property->getType()->getClassName() + . 'DAO' + . EXT_CLASS; + + if (is_readable($oldStyle)) { + $out + ->newLine() + ->error('remove manually: ' . $oldStyle); + } + } + } + } + + return $this; + } + + /** + * @param $orly bool + * @return self + **/ + public function setWithEnumerationRefIntegrityCheck($orly) + { + $this->checkEnumerationRefIntegrity = $orly; + + return $this; + } + + public function isWithEnumerationRefIntegrityCheck() + { + return $this->checkEnumerationRefIntegrity; + } + + protected function checkRecursion(MetaClassProperty $property, MetaClass $holder, $paths = array()) + { + Assert::isTrue( + $property->getRelationId() + == MetaRelation::ONE_TO_ONE + ); + + if ( + $property->getFetchStrategy() + && $property->getFetchStrategy()->getId() != FetchStrategy::JOIN + ) { + return false; + } + + $remote = $property->getType()->getClass(); + + if (isset($paths[$holder->getName()][$remote->getName()])) + return true; + else { + $paths[$holder->getName()][$remote->getName()] = true; + + foreach ($remote->getProperties() as $remoteProperty) { + if ( + $remoteProperty->getRelationId() + == MetaRelation::ONE_TO_ONE + ) { + if ( + $this->checkRecursion( + $remoteProperty, + $holder, + $paths + ) + ) { + $remoteProperty->setFetchStrategy( + FetchStrategy::cascade() + ); + } + } + } + } + + return false; + } + + + protected function checkClassSanity(MetaClass $class, ReflectionClass $info) + { + switch ($class->getTypeId()) { + case null: + break; + + case MetaClassType::CLASS_ABSTRACT: + Assert::isTrue( + $info->isAbstract(), + 'class ' . $info->getName() . ' expected to be abstract' + ); + Assert::isTrue( + $class->getPattern() instanceof AbstractClassPattern, + 'class ' . $info->getName() . ' must use AbstractClassPattern' + ); + break; + + case MetaClassType::CLASS_FINAL: + Assert::isTrue( + $info->isFinal(), + 'class ' . $info->getName() . ' expected to be final' + ); + break; + + case MetaClassType::CLASS_SPOOKED: + default: + Assert::isUnreachable(); + break; + } + + if ($public = $info->getProperties(ReflectionProperty::IS_PUBLIC)) { + Assert::isUnreachable( + $class->getName() + . ' contains properties with evil visibility:' + . "\n" + . print_r($public, true) + ); + } + + return $this; + } + + protected function checkEnumerationReferentialIntegrity($enumeration, $tableName) + { + Assert::isTrue( + ( + $enumeration instanceof Enumeration + || $enumeration instanceof Enum + || $enumeration instanceof Registry + ), + 'argument enumeation must be instacne of Enumeration, Enum or Registry! gived, "' . gettype($enumeration) . '"' + ); + + $updateQueries = null; + + $db = DBPool::me()->getLink(); + + $class = get_class($enumeration); + + /** @var NamedObject[] $list */ + if ($enumeration instanceof Enumeration) + $list = $enumeration::makeObjectList(); + elseif ($enumeration instanceof Enum) + $list = ClassUtils::callStaticMethod($class . '::getList'); + elseif ($enumeration instanceof Registry) + $list = ClassUtils::callStaticMethod($class . '::getList'); + else + throw new WrongArgumentException('dont know how to get list of ' . get_class($enumeration)); + + $ids = array(); + foreach ($list as $enumerationObject) + $ids[$enumerationObject->getId()] = $enumerationObject->getName(); + + $rows = + $db->querySet( + OSQL::select()->from($tableName) + ->multiGet('id', 'name') + ); + + echo "\n"; + + foreach ($rows as $row) { + if (!isset($ids[$row['id']])) + echo "Class '{$class}', strange id: {$row['id']} found. \n"; + else { + if ($ids[$row['id']] != $row['name']) { + echo "Class '{$class}',id: {$row['id']} sync names. \n"; + + $updateQueries .= + OSQL::update($tableName) + ->set('name', $ids[$row['id']]) + ->where(Expression::eq('id', $row['id'])) + ->toDialectString($db->getDialect()) + . ";\n"; + } + + unset($ids[$row['id']]); + } + } + + foreach ($ids as $id => $name) + echo "Class '{$class}', id: {$id} not present in database. \n"; + + echo $updateQueries; + + return $this; + } + + + /** + * @param $name string + * @throws MissingElementException + * @return MetaClass + **/ + public function getClassByName($name) + { + if (isset($this->classes[$name])) + return $this->classes[$name]; + + throw new MissingElementException( + "knows nothing about '{$name}' class" + ); + } + + /** + * @return MetaClass[] + */ + public function getClassList() + { + return $this->classes; + } + + /** + * @param SimpleXMLElement $source + * @return self + **/ + protected function addSource(SimpleXMLElement $source) + { + $name = (string)$source['name']; + + $default = + isset($source['default']) && (string)$source['default'] == 'true' + ? true + : false; + + Assert::isFalse( + isset($this->sources[$name]), + "duplicate source - '{$name}'" + ); + + Assert::isFalse( + $default && $this->defaultSource !== null, + 'too many default sources' + ); + + $this->sources[$name] = $default; + + if ($default) + $this->defaultSource = $name; + + return $this; + } + + /** + * @param SimpleXMLElement $xml + * @param $metafile + * @param $generate + * @return $this + * @throws MissingElementException + * @throws UnimplementedFeatureException + * @throws UnsupportedMethodException + * @throws WrongArgumentException + */ + protected function processClasses(SimpleXMLElement $xml, $metafile, $generate) + { + foreach ($xml->classes[0] as $xmlClass) { + $name = (string)$xmlClass['name']; + + Assert::isFalse( + isset($this->classes[$name]), + 'class name collision found for ' . $name + ); + + $class = new MetaClass($name); + + if (isset($xmlClass['source'])) + $class->setSourceLink((string)$xmlClass['source']); + + if (isset($xmlClass['table'])) + $class->setTableName((string)$xmlClass['table']); + + if (isset($xmlClass['type'])) { + $type = (string)$xmlClass['type']; + + if ($type == 'spooked') { + $this->getOutput() + ->warning($class->getName()) + ->warningLine(': uses obsoleted "spooked" type.') + ->newLine(); + } + + $class->setType( + new MetaClassType( + (string)$xmlClass['type'] + ) + ); + } + + // lazy existence checking + if (isset($xmlClass['extends'])) + $this->liaisons[$class->getName()] = (string)$xmlClass['extends']; + + // populate implemented interfaces + foreach ($xmlClass->implement as $xmlImplement) + $class->addInterface((string)$xmlImplement['interface']); + + if (isset($xmlClass->properties[0]->identifier)) { + + $id = $xmlClass->properties[0]->identifier; + + if (!isset($id['name'])) + $name = 'id'; + else + $name = (string)$id['name']; + + if (!isset($id['type'])) + $type = 'BigInteger'; + else + $type = (string)$id['type']; + + $property = $this->makeProperty( + $name, + $type, + $class, + // not casting to int because of Numeric possible size + (string)$id['size'] + ); + + if (isset($id['column'])) { + $property->setColumnName( + (string)$id['column'] + ); + } elseif ( + $property->getType() instanceof ObjectType + && !$property->getType()->isGeneric() + ) { + $property->setColumnName($property->getConvertedName() . '_id'); + } else { + $property->setColumnName($property->getConvertedName()); + } + + $property + ->setIdentifier(true) + ->required(); + + $class->addProperty($property); + + unset($xmlClass->properties[0]->identifier); + } + + $class->setPattern( + $this->guessPattern((string)$xmlClass->pattern['name']) + ); + + if ((string)$xmlClass->pattern['fetch'] == 'cascade') + $class->setFetchStrategy(FetchStrategy::cascade()); + + if ($class->getPattern() instanceof InternalClassPattern) { + Assert::isTrue( + $metafile === ONPHP_META_PATH . 'internal.xml', + 'internal classes can be defined only in onPHP, sorry' + ); + } elseif ( + ( + $class->getPattern() instanceof SpookedClassPattern + ) || ( + $class->getPattern() instanceof SpookedEnumerationPattern + ) || ( + $class->getPattern() instanceof SpookedEnumPattern + ) || ( + $class->getPattern() instanceof SpookedRegistryPattern + ) + ) { + $class->setType( + new MetaClassType( + MetaClassType::CLASS_SPOOKED + ) + ); + } + + // populate properties + foreach ($xmlClass->properties[0] as $xmlProperty) { + + $property = $this->makeProperty( + (string)$xmlProperty['name'], + (string)$xmlProperty['type'], + $class, + (string)$xmlProperty['size'] + ); + + if (isset($xmlProperty['column'])) { + $property->setColumnName( + (string)$xmlProperty['column'] + ); + } elseif ( + $property->getType() instanceof ObjectType + && !$property->getType()->isGeneric() + ) { + if ( + isset( + $this->classes[$property->getType()->getClassName()] + ) && ( + $property->getType()->getClass()->getPattern() + instanceof InternalClassPattern + ) + ) { + throw new UnimplementedFeatureException( + 'you can not use internal classes directly atm' + ); + } + + $property->setColumnName($property->getConvertedName() . '_id'); + } else if ($property->getType() instanceof ArrayOfEnumerationsType) { + $property->setColumnName($property->getConvertedName() . '_ids'); + } else { + $property->setColumnName($property->getConvertedName()); + } + + if ((string)$xmlProperty['required'] == 'true') + $property->required(); + + if (isset($xmlProperty['identifier'])) { + throw new WrongArgumentException( + 'obsoleted identifier description found in ' + . "{$class->getName()} class;\n" + . 'you must use instead.' + ); + } + + if (!$property->getType()->isGeneric()) { + + if (!isset($xmlProperty['relation'])) + throw new MissingElementException( + 'relation should be set for non-generic ' + . "property '{$property->getName()}' type '" + . get_class($property->getType()) . "'" + . " of '{$class->getName()}' class" + ); + else { + $property->setRelation( + MetaRelation::makeFromName( + (string)$xmlProperty['relation'] + ) + ); + + if ($fetch = (string)$xmlProperty['fetch']) { + Assert::isTrue( + $property->getRelationId() + == MetaRelation::ONE_TO_ONE, + + 'fetch mode can be specified + only for OneToOne relations' + ); + + if ($fetch == 'lazy') + $property->setFetchStrategy( + FetchStrategy::lazy() + ); + elseif ($fetch == 'cascade') + $property->setFetchStrategy( + FetchStrategy::cascade() + ); + else + throw new WrongArgumentException( + 'strange fetch mode found - ' . $fetch + ); + } + + if ( + ( + ( + $property->getRelationId() + == MetaRelation::ONE_TO_ONE + ) && ( + $property->getFetchStrategyId() + != FetchStrategy::LAZY + ) + ) && ( + $property->getType()->getClassName() + <> $class->getName() + ) + ) { + $this->references[$property->getType()->getClassName()][] + = $class->getName(); + } + + if ((string)$xmlProperty['reference'] == 'false') { + $property->skipReference(); + } else { + $property->buildReference(); + } + + } + } + + if (isset($xmlProperty['default'])) { + // will be correctly autocasted further down the code + $property->getType()->setDefault( + (string)$xmlProperty['default'] + ); + } + + $class->addProperty($property); + + /** + * класс содержащий свойство с TranslatedStoreType + * должен реализовывать Translatable, иначе переводы не будут работать + * добавляем Translatable, если его нет + */ + if ($property->getType() instanceof TranslatedStoreType) { + if (!in_array('Translatable', $class->getInterfaces())) { + $class->addInterface('Translatable'); + } + } + } + + $class->setBuild($generate); + + $this->classes[$class->getName()] = $class; + } + + return $this; + } + + /** + * @param string $directory + * @param string $preStrip + * @param string $postStrip + * @param bool $drop + * @return $this + */ + protected function checkDirectory($directory, $preStrip, $postStrip, $drop = false) + { + $out = $this->getOutput(); + + foreach ( + glob($directory . '*.class.php', GLOB_NOSORT) + as $filename + ) { + $name = + substr( + basename($filename, $postStrip . EXT_CLASS), + strlen($preStrip) + ); + + if (!isset($this->classes[$name])) { + $out->warning( + "\t" + . str_replace( + getcwd() . DIRECTORY_SEPARATOR, + null, + $filename + ) + ); + + if ($drop) { + try { + unlink($filename); + $out->infoLine(' removed.'); + } catch (Exception $e) { + $out->errorLine(' failed to remove.'); + } + } else { + $out->newLine(); + } + } + } + + return $this; + } + + /** + * @param string $name + * @param string $type + * @param MetaClass $class + * @param $size + * @return MetaClassProperty + * @throws WrongArgumentException + */ + protected function makeProperty($name, $type, MetaClass $class, $size) + { + Assert::isFalse( + strpos($name, '_'), + 'naming convention violation spotted' + ); + + $parameters = array(); + if (strpos($type, ':')) { + list($type, $parameters) = explode(':', $type, 2); + $parameters = explode(',', $parameters); + } + + if (!$name || !$type) + throw new WrongArgumentException( + 'strange name or type given: "' . $name . '" - "' . $type . '"' + ); + + if (is_readable(ONPHP_META_TYPES . $type . 'Type' . EXT_CLASS)) + $typeClass = $type . 'Type'; + else + $typeClass = 'ObjectType'; + + $property = new MetaClassProperty($name, new $typeClass($type, $parameters), $class); + + if ($size) + $property->setSize($size); + else { + Assert::isTrue( + ( + !$property->getType() + instanceof FixedLengthStringType + ) && ( + !$property->getType() + instanceof NumericType + ) && ( + !$property->getType() + instanceof HttpUrlType + ), + + 'size is required for "' . $property->getName() . '"' + ); + } + + return $property; + } + + /** + * @param string $name + * @throws MissingElementException + * @return GenerationPattern + **/ + protected function guessPattern($name) + { + $class = $name . 'Pattern'; + + if (is_readable(ONPHP_META_PATTERNS . $class . EXT_CLASS)) + return Singleton::getInstance($class); + + throw new MissingElementException( + "unknown pattern '{$name}'" + ); + } + + /** + * @param MetaClass $class + * @return MetaConfiguration + * @throws WrongArgumentException + */ + protected function checkSanity(MetaClass $class) + { + if ( + ( + !$class->getParent() + || ( + $class->getFinalParent()->getPattern() + instanceof InternalClassPattern + ) + ) + && (!$class->getPattern() instanceof ValueObjectPattern) + && (!$class->getPattern() instanceof AbstractClassPattern) + && (!$class->getPattern() instanceof InternalClassPattern) + ) { + Assert::isTrue( + $class->getIdentifier() !== null, + 'only value objects can live without identifiers. ' + . 'do not use them anyway (' + . $class->getName() . ')' + ); + } + + if ( + $class->getType() + && $class->getTypeId() + == MetaClassType::CLASS_SPOOKED + ) { + Assert::isFalse( + count($class->getProperties()) > 1, + 'spooked classes must have only identifier: ' + . $class->getName() + ); + + Assert::isTrue( + ($class->getPattern() instanceof SpookedClassPattern + || $class->getPattern() instanceof SpookedEnumerationPattern + || $class->getPattern() instanceof SpookedEnumPattern + || $class->getPattern() instanceof SpookedRegistryPattern), + 'spooked classes must use spooked patterns only: ' + . $class->getName() + ); + } + + foreach ($class->getProperties() as $property) { + if ( + !$property->getType()->isGeneric() + && $property->getType() instanceof ObjectType + && + $property->getType()->getClass()->getPattern() + instanceof ValueObjectPattern + ) { + Assert::isTrue( + $property->isRequired(), + 'optional value object is not supported:' + . $property->getName() . ' @ ' . $class->getName() + ); + + Assert::isTrue( + $property->getRelationId() == MetaRelation::ONE_TO_ONE, + 'value objects must have OneToOne relation: ' + . $property->getName() . ' @ ' . $class->getName() + + ); + } elseif ( + ($property->getFetchStrategyId() == FetchStrategy::LAZY) + && $property->getType()->isGeneric() + ) { + throw new WrongArgumentException( + 'lazy one-to-one is supported only for ' + . 'non-generic object types ' + . '(' . $property->getName() + . ' @ ' . $class->getName() + . ')' + ); + } + } + + return $this; + } + + /** @return MetaOutput */ + protected function getOutput() { + return $this->meta->getOutput(); + } +} \ No newline at end of file diff --git a/meta/classes/MetaConfigurationPluginInterface.class.php b/meta/classes/MetaConfigurationPluginInterface.class.php new file mode 100644 index 0000000000..a7bbaab47d --- /dev/null +++ b/meta/classes/MetaConfigurationPluginInterface.class.php @@ -0,0 +1,56 @@ + + * @date 2015-11-11 + */ + +interface MetaConfigurationPluginInterface +{ + /** + * IMetaConfigurationPlugin constructor. + * @param MetaConfiguration $metaConfiguration + */ + public function __construct(MetaConfiguration $metaConfiguration); + + /** + * Returns array of replacements for correcting dtd-schema file location + * @return string[] + */ + public function getDtdMapping(); + + /** + * Parses a part of configuration this plugin is responsible for. + * Called multiple times for multiple files. + * @param SimpleXMLElement $config + * @param $metafile + * @param $generate + * @return mixed + */ + public function loadConfig(SimpleXMLElement $config, $metafile, $generate); + + /** + * Performs checks after all configuration is loaded. + * @return void + */ + public function checkConfig(); + + /** + * Generates resulting code + * @return void + */ + public function buildFiles(); + + /** + * Performs checks on generated code. + * @return void + */ + public function checkIntegrity(); + + /** + * Checks if some existing files are no more referenced in meta configuration. + * @param bool $drop remove unreferenced files + * @return void + */ + public function checkForStaleFiles($drop); + +} \ No newline at end of file diff --git a/meta/classes/MetaOutput.class.php b/meta/classes/MetaOutput.class.php old mode 100644 new mode 100755 index d9f5db7e3d..c7d764231a --- a/meta/classes/MetaOutput.class.php +++ b/meta/classes/MetaOutput.class.php @@ -114,9 +114,9 @@ public function remark($text) /** * @return MetaOutput **/ - public function remarkLine($text) + public function remarkLine($text, $color = ConsoleMode::FG_BLUE, $bold = true) { - return $this->defaultTextLine($text, ConsoleMode::FG_BLUE, true); + return $this->defaultTextLine($text, $color, $bold); } /** diff --git a/meta/classes/MetaRelation.class.php b/meta/classes/MetaRelation.class.php old mode 100644 new mode 100755 diff --git a/meta/classes/TextOutput.class.php b/meta/classes/TextOutput.class.php old mode 100644 new mode 100755 diff --git a/meta/dtd/meta.dtd b/meta/dtd/meta.dtd old mode 100644 new mode 100755 index 35a0b81a57..9819979427 --- a/meta/dtd/meta.dtd +++ b/meta/dtd/meta.dtd @@ -1,6 +1,6 @@ - + @@ -38,6 +38,7 @@ required (true|false) "false" relation (OneToOne|OneToMany|ManyToMany) #IMPLIED fetch (lazy|cascade) #IMPLIED + reference (true|false) #IMPLIED > @@ -45,12 +46,19 @@ name ( DictionaryClass |AbstractClass + |AbstractValueObject |StraightMapping |EnumerationClass + |EnumClass + |RegistryClass |SpookedClass |SpookedEnumeration + |SpookedEnum + |SpookedRegistry |ValueObject |InternalClass + |NosqlClass + |ObjectWithEmptyDAOClass ) #REQUIRED fetch (lazy|cascade) #IMPLIED > @@ -68,4 +76,10 @@ + + + \ No newline at end of file diff --git a/meta/internal.xml b/meta/internal.xml old mode 100644 new mode 100755 index 218aa8be50..a5f48ddbf0 --- a/meta/internal.xml +++ b/meta/internal.xml @@ -79,4 +79,27 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/meta/patterns/AbstractClassPattern.class.php b/meta/patterns/AbstractClassPattern.class.php old mode 100644 new mode 100755 index 663add881c..f9372fb43c --- a/meta/patterns/AbstractClassPattern.class.php +++ b/meta/patterns/AbstractClassPattern.class.php @@ -12,7 +12,7 @@ /** * @ingroup Patterns **/ - final class AbstractClassPattern extends BasePattern + class AbstractClassPattern extends BasePattern { public function tableExists() { diff --git a/meta/patterns/AbstractValueObjectPattern.class.php b/meta/patterns/AbstractValueObjectPattern.class.php new file mode 100644 index 0000000000..7b3f6a1271 --- /dev/null +++ b/meta/patterns/AbstractValueObjectPattern.class.php @@ -0,0 +1,28 @@ + + buildBusiness($class)-> + buildProto($class); + } + } +?> \ No newline at end of file diff --git a/meta/patterns/BasePattern.class.php b/meta/patterns/BasePattern.class.php old mode 100644 new mode 100755 index 18db66b740..c929363a6a --- a/meta/patterns/BasePattern.class.php +++ b/meta/patterns/BasePattern.class.php @@ -18,43 +18,43 @@ public function tableExists() { return true; } - + public function daoExists() { return false; } - + public static function dumpFile($path, $content) { $content = trim($content); - + if (is_readable($path)) { $pattern = array( '@\/\*(.*)\*\/@sU', '@[\r\n]@sU' ); - + // strip only header and svn's Id-keyword, don't skip type hints $old = preg_replace($pattern, null, file_get_contents($path), 2); $new = preg_replace($pattern, null, $content, 2); } else { $old = 1; $new = 2; } - + $out = MetaConfiguration::out(); $className = basename($path, EXT_CLASS); - + if ($old !== $new) { $out-> warning("\t\t".$className.' '); - + if (!MetaConfiguration::me()->isDryRun()) { $fp = fopen($path, 'wb'); fwrite($fp, $content); fclose($fp); } - + $out-> log('(')-> remark( @@ -66,12 +66,12 @@ public static function dumpFile($path, $content) infoLine("\t\t".$className.' ', true); } } - + public function build(MetaClass $class) { return $this->fullBuild($class); } - + /** * @return BasePattern **/ @@ -82,7 +82,7 @@ protected function fullBuild(MetaClass $class) buildBusiness($class)-> buildDao($class); } - + /** * @return BasePattern **/ @@ -92,9 +92,9 @@ protected function buildProto(MetaClass $class) ONPHP_META_AUTO_PROTO_DIR.'AutoProto'.$class->getName().EXT_CLASS, Format::indentize(AutoProtoClassBuilder::build($class)) ); - + $userFile = ONPHP_META_PROTO_DIR.'Proto'.$class->getName().EXT_CLASS; - + if ( MetaConfiguration::me()->isForcedGeneration() || !file_exists($userFile) @@ -103,10 +103,10 @@ protected function buildProto(MetaClass $class) $userFile, Format::indentize(ProtoClassBuilder::build($class)) ); - + return $this; } - + /** * @return BasePattern **/ @@ -116,9 +116,9 @@ protected function buildBusiness(MetaClass $class) ONPHP_META_AUTO_BUSINESS_DIR.'Auto'.$class->getName().EXT_CLASS, Format::indentize(AutoClassBuilder::build($class)) ); - + $userFile = ONPHP_META_BUSINESS_DIR.$class->getName().EXT_CLASS; - + if ( MetaConfiguration::me()->isForcedGeneration() || !file_exists($userFile) @@ -127,10 +127,10 @@ protected function buildBusiness(MetaClass $class) $userFile, Format::indentize(BusinessClassBuilder::build($class)) ); - + return $this; } - + /** * @return BasePattern **/ @@ -140,9 +140,33 @@ protected function buildDao(MetaClass $class) ONPHP_META_AUTO_DAO_DIR.'Auto'.$class->getName().'DAO'.EXT_CLASS, Format::indentize(AutoDaoBuilder::build($class)) ); - + + $userFile = ONPHP_META_DAO_DIR.$class->getName().'DAO'.EXT_CLASS; + + if ( + MetaConfiguration::me()->isForcedGeneration() + || !file_exists($userFile) + ) + $this->dumpFile( + $userFile, + Format::indentize(DaoBuilder::build($class)) + ); + + return $this; + } + + /** + * @return BasePattern + **/ + protected function buildNoSqlDao(MetaClass $class) + { + $this->dumpFile( + ONPHP_META_AUTO_DAO_DIR.'Auto'.$class->getName().'DAO'.EXT_CLASS, + Format::indentize(AutoNoSqlDaoBuilder::build($class)) + ); + $userFile = ONPHP_META_DAO_DIR.$class->getName().'DAO'.EXT_CLASS; - + if ( MetaConfiguration::me()->isForcedGeneration() || !file_exists($userFile) @@ -151,7 +175,7 @@ protected function buildDao(MetaClass $class) $userFile, Format::indentize(DaoBuilder::build($class)) ); - + return $this; } } diff --git a/meta/patterns/DictionaryClassPattern.class.php b/meta/patterns/DictionaryClassPattern.class.php old mode 100644 new mode 100755 diff --git a/meta/patterns/EnumClassPattern.class.php b/meta/patterns/EnumClassPattern.class.php new file mode 100644 index 0000000000..484e1551e2 --- /dev/null +++ b/meta/patterns/EnumClassPattern.class.php @@ -0,0 +1,46 @@ +getName().EXT_CLASS; + + if ( + MetaConfiguration::me()->isForcedGeneration() + || !file_exists($userFile) + ) + $this->dumpFile( + $userFile, + Format::indentize(EnumClassBuilder::build($class)) + ); + + return $this; + } + } +?> \ No newline at end of file diff --git a/meta/patterns/EnumerationClassPattern.class.php b/meta/patterns/EnumerationClassPattern.class.php old mode 100644 new mode 100755 diff --git a/meta/patterns/GenerationPattern.class.php b/meta/patterns/GenerationPattern.class.php old mode 100644 new mode 100755 diff --git a/meta/patterns/InternalClassPattern.class.php b/meta/patterns/InternalClassPattern.class.php old mode 100644 new mode 100755 diff --git a/meta/patterns/NosqlClassPattern.class.php b/meta/patterns/NosqlClassPattern.class.php new file mode 100755 index 0000000000..92fe43b6b5 --- /dev/null +++ b/meta/patterns/NosqlClassPattern.class.php @@ -0,0 +1,40 @@ + + buildProto($class)-> + buildBusiness($class)-> + buildNoSqlDao($class); + } + +} diff --git a/meta/patterns/ObjectWithEmptyDAOClassPattern.class.php b/meta/patterns/ObjectWithEmptyDAOClassPattern.class.php new file mode 100644 index 0000000000..d3896c304d --- /dev/null +++ b/meta/patterns/ObjectWithEmptyDAOClassPattern.class.php @@ -0,0 +1,53 @@ + + * @date 2013.04.19 + */ + +final class ObjectWithEmptyDAOClassPattern extends BasePattern { + + public function tableExists() + { + return false; + } + + public function daoExists() + { + return true; + } + + /** + * @param $class MetaClass + * @return self + **/ + protected function fullBuild(MetaClass $class) + { + return $this + ->buildBusiness($class) + ->buildProto($class) + ->buildDao($class); + } + + protected function buildDao(MetaClass $class) { + $this->dumpFile( + ONPHP_META_AUTO_DAO_DIR.'Auto'.$class->getName().'DAO'.EXT_CLASS, + Format::indentize(AutoEmptyDaoBuilder::build($class)) + ); + + $userFile = ONPHP_META_DAO_DIR.$class->getName().'DAO'.EXT_CLASS; + + if ( + MetaConfiguration::me()->isForcedGeneration() + || !file_exists($userFile) + ) + $this->dumpFile( + $userFile, + Format::indentize(DaoBuilder::build($class)) + ); + + return $this; + } + + +} \ No newline at end of file diff --git a/meta/patterns/RegistryClassPattern.class.php b/meta/patterns/RegistryClassPattern.class.php new file mode 100644 index 0000000000..649b96b9af --- /dev/null +++ b/meta/patterns/RegistryClassPattern.class.php @@ -0,0 +1,45 @@ +getName().EXT_CLASS; + + if ( + MetaConfiguration::me()->isForcedGeneration() + || !file_exists($userFile) + ) + $this->dumpFile( + $userFile, + Format::indentize(RegistryClassBuilder::build($class)) + ); + + return $this; + } + } \ No newline at end of file diff --git a/meta/patterns/SpookedClassPattern.class.php b/meta/patterns/SpookedClassPattern.class.php old mode 100644 new mode 100755 diff --git a/meta/patterns/SpookedEnumPattern.class.php b/meta/patterns/SpookedEnumPattern.class.php new file mode 100644 index 0000000000..16c4b57a39 --- /dev/null +++ b/meta/patterns/SpookedEnumPattern.class.php @@ -0,0 +1,30 @@ + \ No newline at end of file diff --git a/meta/patterns/SpookedEnumerationPattern.class.php b/meta/patterns/SpookedEnumerationPattern.class.php old mode 100644 new mode 100755 diff --git a/meta/patterns/SpookedRegistryPattern.class.php b/meta/patterns/SpookedRegistryPattern.class.php new file mode 100644 index 0000000000..49889b9e1f --- /dev/null +++ b/meta/patterns/SpookedRegistryPattern.class.php @@ -0,0 +1,29 @@ + + * @date 14.01.2015 + */ + +class ArrayOfEnumerationsType extends ArrayOfIntegersType { + + /** @var MetaClass */ + protected $enumerationClass; + + function __construct($type, array $parameters) { + Assert::isNotEmptyArray($parameters, 'enumeration class name is not provided'); + list($enumerationClassName) = $parameters; + + $this->enumerationClass = MetaConfiguration::me()->getCorePlugin()->getClassByName($enumerationClassName); + + Assert::isTrue( + $this->enumerationClass->getPattern() instanceof EnumerationClassPattern, + 'only enumeration classes can be provided for ArrayOfEnumerations type' + ); + } + + public function toGetter( + MetaClass $class, + MetaClassProperty $property, + MetaClassProperty $holder = null + ) + { + if ($holder) + $name = $holder->getName().'->get'.ucfirst($property->getName()).'()'; + else + $name = $property->getName(); + + $methodName = 'get'.ucfirst($property->getName()).'List'; + + return parent::toGetter($class, $property, $holder) . <<enumerationClass->getName()}[] +**/ +public function {$methodName}() +{ + return array_map(array('{$this->enumerationClass->getName()}', 'create'), \$this->{$name}); +} + +EOT; + } + + public function toSetter( + MetaClass $class, + MetaClassProperty $property, + MetaClassProperty $holder = null + ) + { + $name = $property->getName(); + $methodName = 'set'.ucfirst($name).'List'; + + $default = $property->isRequired() ? '' : ' = null'; + + if ($holder) { + Assert::isUnreachable(); + } else { + return parent::toSetter($class, $property, $holder) . <<enumerationClass->getName()}[] + * @return {$class->getName()} +**/ +public function {$methodName}(array \${$name}{$default}) +{ + \$this->{$name} = ArrayUtils::getIdsArray(\${$name}); + + return \$this; +} + +EOT; + } + + Assert::isUnreachable(); + } + +} \ No newline at end of file diff --git a/meta/types/ArrayOfFloatsType.class.php b/meta/types/ArrayOfFloatsType.class.php new file mode 100644 index 0000000000..74b17cbde9 --- /dev/null +++ b/meta/types/ArrayOfFloatsType.class.php @@ -0,0 +1,18 @@ + + * @date 2015.03.03 + */ + +class ArrayOfFloatsType extends ArrayType { + + public function getPrimitiveName() { + return 'arrayOfFloats'; + } + + public function toColumnType() { + return 'DataType::create(DataType::SET_OF_FLOATS)'; + } + +} \ No newline at end of file diff --git a/meta/types/ArrayOfIntegersType.class.php b/meta/types/ArrayOfIntegersType.class.php new file mode 100644 index 0000000000..c20f9f6ffc --- /dev/null +++ b/meta/types/ArrayOfIntegersType.class.php @@ -0,0 +1,18 @@ + + * @date 2013.04.02 + */ + +class ArrayOfIntegersType extends ArrayType { + + public function getPrimitiveName() { + return 'arrayOfIntegers'; + } + + public function toColumnType() { + return 'DataType::create(DataType::SET_OF_INTEGERS)'; + } + +} \ No newline at end of file diff --git a/meta/types/ArrayOfStringsType.class.php b/meta/types/ArrayOfStringsType.class.php new file mode 100644 index 0000000000..a757a96f99 --- /dev/null +++ b/meta/types/ArrayOfStringsType.class.php @@ -0,0 +1,18 @@ + + * @date 2013.04.02 + */ + +class ArrayOfStringsType extends ArrayType { + + public function getPrimitiveName() { + return 'arrayOfStrings'; + } + + public function toColumnType() { + return 'DataType::create(DataType::SET_OF_STRINGS)'; + } + +} \ No newline at end of file diff --git a/meta/types/ArrayType.class.php b/meta/types/ArrayType.class.php new file mode 100644 index 0000000000..bfcb4a4c9e --- /dev/null +++ b/meta/types/ArrayType.class.php @@ -0,0 +1,84 @@ + + * @date 2012.08.02 + */ +class ArrayType extends BasePropertyType { + + public function getPrimitiveName() { + return 'set'; + } + + public function getDeclaration() { + return 'array()'; + } + + public function isMeasurable() { + return true; + } + + public function toColumnType() { + throw new UnimplementedFeatureException('ArrayType should not be used within SQL databases'); + } + + + public function toGetter( + MetaClass $class, + MetaClassProperty $property, + MetaClassProperty $holder = null + ) + { + if ($holder) + $name = $holder->getName().'->get'.ucfirst($property->getName()).'()'; + else + $name = $property->getName(); + + $methodName = 'get'.ucfirst($property->getName()); + + return <<{$name}; +} + +EOT; + } + + public function toSetter( + MetaClass $class, + MetaClassProperty $property, + MetaClassProperty $holder = null + ) + { + $name = $property->getName(); + $methodName = 'set'.ucfirst($name); + + $default = $property->isRequired() ? '' : ' = null'; + + if ($holder) { + Assert::isUnreachable(); + } else { + return <<getName()} +**/ +public function {$methodName}(array \${$name}{$default}) +{ + \$this->{$name} = \${$name}; + + return \$this; +} + +EOT; + } + + Assert::isUnreachable(); + } + +} diff --git a/meta/types/BasePropertyType.class.php b/meta/types/BasePropertyType.class.php old mode 100644 new mode 100755 index 04049505a9..180b858784 --- a/meta/types/BasePropertyType.class.php +++ b/meta/types/BasePropertyType.class.php @@ -18,14 +18,14 @@ abstract public function getDeclaration(); abstract public function isMeasurable(); abstract public function toColumnType(); abstract public function getPrimitiveName(); - + protected $default = null; - + public function isGeneric() { return true; } - + public function toMethods( MetaClass $class, MetaClassProperty $property, @@ -34,26 +34,27 @@ public function toMethods( { return $this->toGetter($class, $property, $holder) + .( $this->hasDefault() ? $this->toGetterDefault($class, $property, $holder) : '' ) .$this->toSetter($class, $property, $holder); } - + public function hasDefault() { return ($this->default !== null); } - + public function getDefault() { return $this->default; } - + public function setDefault($default) { throw new UnsupportedMethodException( 'only generic non-object types can have default values atm' ); } - + public function toGetter( MetaClass $class, MetaClassProperty $property, @@ -64,9 +65,9 @@ public function toGetter( $name = $holder->getName().'->get'.ucfirst($property->getName()).'()'; else $name = $property->getName(); - + $methodName = 'get'.ucfirst($property->getName()); - + return <<getName().'->get'.ucfirst($property->getName()).'()'; + else + $name = $property->getName(); + + $methodName = 'getDefault'.ucfirst($property->getName()); + + return <<getType()->getDeclaration()}; +} + +EOT; + } + public function toSetter( MetaClass $class, MetaClassProperty $property, @@ -85,7 +109,7 @@ public function toSetter( { $name = $property->getName(); $methodName = 'set'.ucfirst($name); - + if ($holder) { return << \ No newline at end of file diff --git a/meta/types/HttpUrlType.class.php b/meta/types/HttpUrlType.class.php old mode 100644 new mode 100755 diff --git a/meta/types/InetType.class.php b/meta/types/InetType.class.php old mode 100644 new mode 100755 diff --git a/meta/types/IntegerType.class.php b/meta/types/IntegerType.class.php old mode 100644 new mode 100755 diff --git a/meta/types/InternalType.class.php b/meta/types/InternalType.class.php old mode 100644 new mode 100755 diff --git a/meta/types/IpAddressType.class.php b/meta/types/IpAddressType.class.php old mode 100644 new mode 100755 diff --git a/meta/types/IpCidrRangeType.class.php b/meta/types/IpCidrRangeType.class.php new file mode 100755 index 0000000000..dfb2a730ef --- /dev/null +++ b/meta/types/IpCidrRangeType.class.php @@ -0,0 +1,33 @@ + + * @date 2013.12.02 + */ + + /** + * @ingroup Types + **/ + class IpCidrRangeType extends ObjectType + { + public function getPrimitiveName() + { + return 'IpCidrRange'; + } + + public function isGeneric() + { + return true; + } + + public function isMeasurable() + { + return true; + } + + public function toColumnType() + { + return 'DataType::create(DataType::CIDR)'; + } + } +?> \ No newline at end of file diff --git a/meta/types/IpRangeType.class.php b/meta/types/IpRangeType.class.php old mode 100644 new mode 100755 diff --git a/meta/types/JsonType.class.php b/meta/types/JsonType.class.php new file mode 100644 index 0000000000..23e1a903b8 --- /dev/null +++ b/meta/types/JsonType.class.php @@ -0,0 +1,29 @@ + \ No newline at end of file diff --git a/meta/types/JsonbType.class.php b/meta/types/JsonbType.class.php new file mode 100644 index 0000000000..7d961e21c1 --- /dev/null +++ b/meta/types/JsonbType.class.php @@ -0,0 +1,28 @@ + \ No newline at end of file diff --git a/meta/types/NumericType.class.php b/meta/types/NumericType.class.php old mode 100644 new mode 100755 diff --git a/meta/types/ObjectType.class.php b/meta/types/ObjectType.class.php old mode 100644 new mode 100755 index 525c77f407..414df18a80 --- a/meta/types/ObjectType.class.php +++ b/meta/types/ObjectType.class.php @@ -31,7 +31,7 @@ public function __construct($className) **/ public function getClass() { - return MetaConfiguration::me()->getClassByName($this->className); + return MetaConfiguration::me()->getCorePlugin()->getClassByName($this->className); } public function getClassName() @@ -99,9 +99,11 @@ public function {$methodName}() if ($property->getFetchStrategyId() == FetchStrategy::LAZY) { $className = $property->getType()->getClassName(); - $isEnumeration = - $property->getType()->getClass()->getPattern() - instanceof EnumerationClassPattern; + $isEnumeration = ( + $property->getType()->getClass()->getPattern() instanceof EnumerationClassPattern + || $property->getType()->getClass()->getPattern() instanceof EnumClassPattern + || $property->getType()->getClass()->getPattern() instanceof RegistryClassPattern + ); $fetchObjectString = $isEnumeration ? "new {$className}(\$this->{$name}Id)" @@ -332,9 +334,9 @@ public function {$methodName}() return $method; } - public function toColumnType() + public function toColumnType($length = null) { - return $this->getClass()->getIdentifier()->getType()->toColumnType(); + return $this->getClass()->getIdentifier()->getType()->toColumnType($length); } public function getHint() diff --git a/meta/types/RangeType.class.php b/meta/types/RangeType.class.php old mode 100644 new mode 100755 diff --git a/meta/types/SmallIntegerType.class.php b/meta/types/SmallIntegerType.class.php old mode 100644 new mode 100755 diff --git a/meta/types/StringType.class.php b/meta/types/StringType.class.php old mode 100644 new mode 100755 diff --git a/meta/types/TimeType.class.php b/meta/types/TimeType.class.php old mode 100644 new mode 100755 diff --git a/meta/types/TimestampRangeType.class.php b/meta/types/TimestampRangeType.class.php old mode 100644 new mode 100755 diff --git a/meta/types/TimestampTZType.class.php b/meta/types/TimestampTZType.class.php new file mode 100644 index 0000000000..0a4ddd8513 --- /dev/null +++ b/meta/types/TimestampTZType.class.php @@ -0,0 +1,27 @@ +setTimezoned(true)'; + } +} +?> diff --git a/meta/types/TimestampType.class.php b/meta/types/TimestampType.class.php old mode 100644 new mode 100755 diff --git a/meta/types/TranslatedStoreType.class.php b/meta/types/TranslatedStoreType.class.php new file mode 100644 index 0000000000..fd3a9e90b7 --- /dev/null +++ b/meta/types/TranslatedStoreType.class.php @@ -0,0 +1,115 @@ +getName(); + + $methodName = 'get'.ucfirst($property->getName()); + $classHint = $classHint = $property->getType()->getHint(); + + return <<{$name}) { + \$this->{$name} = new {$this->getClassName()}(); + } + return \$this->{$name}; +} + +/** + * @param string \$langCode + * @return string|null + **/ +public function {$methodName}(\$langCode = null) +{ + /** @var \$store {$this->getClassName()} */ + \$store = \$this->{$methodName}Store(); + + if (\$this->useTranslatedStore()) { + return \$store; + } + + \$languageCodes = array_merge( + array( + \$langCode ?: self::getLanguageCode(), + self::getDefaultLanguageCode() + ), + self::getLanguageCodes() + ); + + foreach (\$languageCodes as \$langCode) { + if (\$store->has(\$langCode)) { + return \$store->get(\$langCode); + } + } + + return null; +} + +EOT; + } + + public function toSetter( + MetaClass $class, + MetaClassProperty $property, + MetaClassProperty $holder = null + ) { + $name = $property->getName(); + $methodNamePart = ucfirst($name); + return <<getName()} +**/ +public function set{$methodNamePart}(\${$name}, \$langCode = null) +{ + if (\$this->useTranslatedStore()) { + \$this->{$name} = \${$name}; + return \$this; + } + + if (!\$langCode) { + \$langCode = self::getLanguageCode(); + } + + \$store = \$this->get{$methodNamePart}Store(); + \$store->set(\$langCode, \${$name}); + + return \$this; +} + +EOT; + } +} \ No newline at end of file diff --git a/meta/types/UnsignedBigIntegerType.class.php b/meta/types/UnsignedBigIntegerType.class.php old mode 100644 new mode 100755 diff --git a/meta/types/UnsignedIntegerType.class.php b/meta/types/UnsignedIntegerType.class.php old mode 100644 new mode 100755 diff --git a/meta/types/UnsignedSmallIntegerType.class.php b/meta/types/UnsignedSmallIntegerType.class.php old mode 100644 new mode 100755 diff --git a/meta/types/UuidType.class.php b/meta/types/UuidType.class.php new file mode 100755 index 0000000000..b064c3595c --- /dev/null +++ b/meta/types/UuidType.class.php @@ -0,0 +1,31 @@ +begin(); $currentPath = get_include_path(); if ($currentPath != $path) { @@ -47,6 +52,10 @@ public static function classPathCache($classname) if (isset($cache[$classname])) { try { include $cache[$cache[$classname]].$classname.EXT_CLASS; + $profiling + ->setInfo($cache[$cache[$classname]].$classname.EXT_CLASS) + ->end() + ; return /* void */; } catch (ClassNotFoundException $e) { throw $e; @@ -62,7 +71,7 @@ public static function classPathCache($classname) foreach (explode(PATH_SEPARATOR, get_include_path()) as $directory) { $cache[$dirCount] = realpath($directory).DIRECTORY_SEPARATOR; - + if ($paths = glob($cache[$dirCount].'*'.EXT_CLASS, GLOB_NOSORT)) { foreach ($paths as $class) { $class = basename($class, EXT_CLASS); @@ -93,6 +102,10 @@ public static function classPathCache($classname) try { include $fileName; + $profiling + ->setInfo($fileName) + ->end() + ; } catch (BaseException $e) { if (is_readable($fileName)) // class compiling failed @@ -105,13 +118,9 @@ public static function classPathCache($classname) } } else { // ok, last chance to find class in non-cached include_path - try { - include $classname.EXT_CLASS; - $cache[ONPHP_CLASS_CACHE_CHECKSUM] = null; - return /* void */; - } catch (BaseException $e) { - __autoload_failed($classname, $e->getMessage()); - } + self::noCache($classname); + $cache[ONPHP_CLASS_CACHE_CHECKSUM] = null; + return /* void */; } } @@ -121,12 +130,38 @@ public static function noCache($classname) /* are you sane? */ return; } - - try { - include $classname.EXT_CLASS; - return /* void */; - } catch (BaseException $e) { - return __autoload_failed($classname, $e->getMessage()); + + // make namespaces work + $classname = str_replace('\\', '/', $classname); + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('autoloader', 'noCache'))->begin(); + + $errors = array(); + foreach (array(EXT_CLASS, '.php') as $ext) { + $filename = stream_resolve_include_path($classname . $ext); + if (!$filename) { + continue; + } + + try { + include $filename; + $profiling + ->setInfo($filename) + ->end() + ; + return /* void */; + } catch (BaseException $e) { + $errors[] = $e->getMessage(); + $profiling + ->setInfo($e->getMessage()) + ->end() + ; + throw $e; + } + } + + if (!class_exists($classname)) { + __autoload_failed($classname, 'class not found' . PHP_EOL . implode(PHP_EOL, $errors)); } } @@ -143,7 +178,9 @@ public static function wholeClassCache($classname) /* are you sane? */ return; } - + + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('autoloader', 'wholeClassCache'))->begin(); $currentPath = get_include_path(); if ($currentPath != $path) { @@ -192,6 +229,10 @@ public static function wholeClassCache($classname) $class = file_get_contents($classPath); eval('?>'.$class); + $profiling + ->setInfo($classPath) + ->end() + ; } catch (BaseException $e) { return __autoload_failed($classname, $e->getMessage()); } @@ -212,7 +253,275 @@ public static function autoloadCleanup() } } } - - + + public static function classPathSharedMemCache($classname) { + if (strpos($classname, "\0") !== false) { + /* are you sane? */ + return; + } + + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('autoloader', 'classPathSharedMemCache'))->begin(); + // make namespaces work + $desiredName = str_replace('\\', '/', $classname); + if($desiredName{0}=='/') { + $desiredName = substr($desiredName, 1); + } + + try { + $realPath = AutoloaderShmop::get($desiredName); + } catch(Exception $e) { + $realPath = null; + } + + if( isset($realPath) && !is_null($realPath) && file_exists($realPath) ) { + include_once $realPath; + $profiling + ->setInfo($realPath) + ->end() + ; + } else { + foreach (explode(PATH_SEPARATOR, get_include_path()) as $directory) { + $directory = realpath($directory); + foreach (array(EXT_CLASS, EXT_LIB) as $ext) { + $realPath = $directory.DIRECTORY_SEPARATOR.$desiredName.$ext; + if( $realPath && file_exists($realPath) && is_readable($realPath) ) { + break; + } else { + $realPath = null; + } + } + if( !is_null($realPath) ) { + break; + } + } + if( !is_null($realPath) ) { + AutoloaderShmop::set($desiredName, $realPath); + include_once $realPath; + $profiling + ->setInfo($realPath) + ->end() + ; + } + } + + if (!class_exists($classname, false) && !interface_exists($classname, false) && !trait_exists($classname, false)) { + __autoload_failed($classname, 'class not found'); + } + } + + protected static $memcahe = null; + + public static function classPathLocalMemcached($classname) { + if (strpos($classname, "\0") !== false) { + /* are you sane? */ + return; + } + + /** @var Profiling $profiling */ + $profiling = Profiling::create(array('autoloader', 'classPathLocalMemcached'))->begin(); + + if( is_null(self::$memcahe) ) { + self::$memcahe = new Memcache(); + self::$memcahe->pconnect('localhost', 11211); + } + + // make namespaces work + $desiredName = str_replace('\\', '/', $classname); + if($desiredName{0}=='/') { + $desiredName = substr($desiredName, 1); + } + + try { + $realPath = self::$memcahe->get($desiredName); + } catch(Exception $e) { + $realPath = null; + } + + if( isset($realPath) && !is_null($realPath) && file_exists($realPath) ) { + include_once $realPath; + $profiling + ->setInfo($realPath) + ->end() + ; + } else { + foreach (explode(PATH_SEPARATOR, get_include_path()) as $directory) { + $directory = realpath($directory); + foreach (array(EXT_CLASS, EXT_LIB) as $ext) { + $realPath = $directory.DIRECTORY_SEPARATOR.$desiredName.$ext; + if( $realPath && file_exists($realPath) && is_readable($realPath) ) { + break; + } else { + $realPath = null; + } + } + if( !is_null($realPath) ) { + break; + } + } + if( !is_null($realPath) ) { + self::$memcahe->set($desiredName, $realPath, 0, 300); + include_once $realPath; + $profiling + ->setInfo($realPath) + ->end() + ; + } + } + + if (!class_exists($classname, false) && !interface_exists($classname, false) && !trait_exists($classname, false)) { + __autoload_failed($classname, 'class not found'); + } + } } + + abstract class AutoloaderShmop { + + /** + * Default shm key, unique enough + * @const string + */ + const DEFAULT_SHM_KEY = 'onphp-autoloader-shmop-cache'; + + /** + * Default shared memory segment size (16 MB) + * @const int + */ + const DEFAULT_SHM_SIZE = 16777216; + + /** + * Size of field which stores data array size + * Starts at zero byte of shm segment + * @const int + */ + const SHM_DATA_OFFSET = 24; + + /** + * Shared segment id + * @var int|null + */ + protected static $shm = null; + + /** + * Temp storage + * @var array|null + */ + protected static $storage = null; + + /** + * storage size after loading + * @var int + */ + protected static $storageSize = 0; + + /** + * shm segment size on load + * @var int + */ + protected static $segmentSize = 0; + + /** + * Get value from storage + * @param string $key + * @return string|null + */ + public static function get($key) { + if(!self::check()) return null; + return self::has($key) ? self::$storage[$key] : null; + } + + /** + * Store data to storage + * @param string $key + * @param string $value + * @return string + */ + public static function set($key, $value) { + if(!self::check()) return null; + self::$storage[$key] = $value; + return $value; + } + + /** + * Check if storage has given key + * @param string $key + * @return bool + */ + public static function has($key) { + if(!self::check()) return null; + return isset(self::$storage[$key]); + } + + public static function save($force = false) { + if(!self::check()) return null; + if( !$force && count(self::$storage)<=self::$storageSize ) { + return self::$segmentSize; + } + $size = shmop_write(self::$shm, json_encode(self::$storage), self::SHM_DATA_OFFSET); + return self::updateSize($size); + } + + public static function clean() { + self::$storage = array(); + self::save(); + } + + protected static function check() { + if( is_null(self::$shm) && is_null(self::$storage) ) { + self::initSegment(); + self::load(); + } + return self::$shm; + } + + /** + * @return int|null + * @throws RuntimeException + */ + protected static function initSegment() { + if( is_null(self::$shm) ) { + $identifier = crc32( defined('ONPHP_CLASS_CACHE_KEY') ? ONPHP_CLASS_CACHE_KEY : self::DEFAULT_SHM_KEY ); + + // Attempt to open shm segment + self::$shm = @shmop_open($identifier, 'w', 0777, self::DEFAULT_SHM_SIZE); + + // If segment doesn't exist init new segment + if (false === self::$shm) { + self::$shm = shmop_open($identifier, 'c', 0777, self::DEFAULT_SHM_SIZE); + + if (false === self::$shm) { + throw new RuntimeException('Unable to create shared memory segment with key: '.$identifier); + } + + self::updateSize(0); + } + } + return self::$shm; + } + + protected static function load() { + self::$segmentSize = intval(shmop_read(self::$shm, 0, self::SHM_DATA_OFFSET)); + + if( self::$segmentSize === 0 ) { + self::$storage = array(); + } else { + $data = shmop_read(self::$shm, self::SHM_DATA_OFFSET, self::$segmentSize); + self::$storage = json_decode($data, true); + } + self::$storageSize = count(self::$storage); + + return self::$segmentSize; + } + + /** + * Update size field + * @param int size + * @return bool + */ + protected static function updateSize($size) { + self::$segmentSize = sprintf('%' . self::SHM_DATA_OFFSET . 'd', intval($size)); + return shmop_write(self::$shm, $size, 0); + } + } + ?> \ No newline at end of file diff --git a/misc/BaseException.inc.php b/misc/BaseException.inc.php old mode 100644 new mode 100755 diff --git a/test/AllTests.php b/test/AllTests.php old mode 100644 new mode 100755 diff --git a/test/config.inc.php.tpl b/test/config.inc.php.tpl old mode 100644 new mode 100755 diff --git a/test/core/AggregateCacheTest.class.php b/test/core/AggregateCacheTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/AssertTest.class.php b/test/core/AssertTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/BaseCachesTest.class.php b/test/core/BaseCachesTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/CalendarDayTest.class.php b/test/core/CalendarDayTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/DateRangeTest.class.php b/test/core/DateRangeTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/DateTest.class.php b/test/core/DateTest.class.php old mode 100644 new mode 100755 index b3efeef692..acb5f8d37d --- a/test/core/DateTest.class.php +++ b/test/core/DateTest.class.php @@ -67,6 +67,16 @@ public function testDateComparsion() return $this; } + public function testClone() + { + $date1 = Date::makeToday(); + $date2 = clone $date1; + + $this->assertFalse( + $date1->getDateTime() === $date2->getDateTime() + ); + } + /** * @test **/ diff --git a/test/core/DeleteQueryTest.class.php b/test/core/DeleteQueryTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/FiltersTest.class.php b/test/core/FiltersTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/FormPrimitivesDateTest.class.php b/test/core/FormPrimitivesDateTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/FormTest.class.php b/test/core/FormTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/IntervalUnitTest.class.php b/test/core/IntervalUnitTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/Ip4ContainsExpressionTest.class.php b/test/core/Ip4ContainsExpressionTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/LockerTest.class.php b/test/core/LockerTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/LogicTest.class.php b/test/core/LogicTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/PrimitiveAliasTest.class.php b/test/core/PrimitiveAliasTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/PrimitiveClassTest.class.php b/test/core/PrimitiveClassTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/PrimitiveDateRangeTest.class.php b/test/core/PrimitiveDateRangeTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/PrimitiveEnumTest.class.php b/test/core/PrimitiveEnumTest.class.php new file mode 100644 index 0000000000..585e5be89c --- /dev/null +++ b/test/core/PrimitiveEnumTest.class.php @@ -0,0 +1,61 @@ + + add( + Primitive::enum('enum') + ->of('MimeType') + ->setDefault($default) + ); + + $form->import(array('enum' => $importId)); + + $this->assertEquals($form->getValue('enum')->getId(), $importId); + $this->assertSame($form->getValue('enum')->getId(), $importId); + + $this->assertEquals($form->getChoiceValue('enum'), $imported->getName()); + $this->assertSame($form->getChoiceValue('enum'), $imported->getName()); + + $form->clean(); + + $this->assertNull($form->getValue('enum')); + $this->assertNull($form->getChoiceValue('enum')); + $this->assertEquals($form->getActualValue('enum')->getId(), $defaultId); + $this->assertEquals($form->getActualChoiceValue('enum'), $default->getName()); + + } + + public function testGetList() + { + $primitive = Primitive::enum('enum')->of('MimeType'); + $enum = MimeType::wrap(1); + + $this->assertEquals($primitive->getList(), MimeType::getObjectList()); + + $primitive->setDefault($enum); + $this->assertEquals($primitive->getList(), MimeType::getObjectList()); + + $primitive->import(array('enum' => MimeType::getAnyId())); + $this->assertEquals($primitive->getList(), MimeType::getObjectList()); + } + } +?> \ No newline at end of file diff --git a/test/core/PrimitiveEnumerationTest.class.php b/test/core/PrimitiveEnumerationTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/PrimitiveHstoreTest.class.php b/test/core/PrimitiveHstoreTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/PrimitiveIdentifierTest.class.php b/test/core/PrimitiveIdentifierTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/PrimitiveInetTest.class.php b/test/core/PrimitiveInetTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/PrimitiveIpTest.class.php b/test/core/PrimitiveIpTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/PrimitiveNumberTest.class.php b/test/core/PrimitiveNumberTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/PrimitiveStringTest.class.php b/test/core/PrimitiveStringTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/PrimitiveTimeTest.class.php b/test/core/PrimitiveTimeTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/PrimitiveTimestampTZTest.class.php b/test/core/PrimitiveTimestampTZTest.class.php new file mode 100644 index 0000000000..04d2674a7a --- /dev/null +++ b/test/core/PrimitiveTimestampTZTest.class.php @@ -0,0 +1,73 @@ +format('O'); + + $prm = Primitive::timestampTZ('test')->setComplex(); + + $array = array( + 'test' => array( + PrimitiveDate::DAY => '1', + PrimitiveDate::MONTH => '2', + PrimitiveDate::YEAR => '', + PrimitiveTimestamp::HOURS => '17', + PrimitiveTimestamp::MINUTES => '38', + PrimitiveTimestamp::SECONDS => '59', + PrimitiveTimestampTZ::ZONE => $currentTimeZone->getName(), + ) + ); + + $this->assertFalse($prm->import($array)); + $this->assertEquals($array['test'], $prm->getRawValue()); + + $this->assertEmpty( + array_filter($prm->exportValue()) + ); + + //not supported other epochs + $array['test'][PrimitiveDate::YEAR] = '3456'; + $this->assertTrue($prm->import($array)); + $this->assertEquals(3456, $prm->getValue()->getYear()); + $this->assertEquals(17, $prm->getValue()->getHour()); + + $this->assertEquals( + $array['test'], + $prm->exportValue() + ); + + $array['test'][PrimitiveDate::YEAR] = '2012'; + + $this->assertTrue($prm->import($array)); + $this->assertEquals( + '2012-02-01 17:38:59'.$zone, + $prm->getValue()->toString() + ); + } + + public function testSingle() + { + $prm = Primitive::timestampTZ('test')->setSingle(); + + $array = array('test' => '1234-01-02 17:38:59'); + + $this->assertTrue($prm->import($array)); + $this->assertEquals(1234, $prm->getValue()->getYear()); + + $array = array('test' => '1975-01-02 17:38:59'); + + $this->assertTrue($prm->import($array)); + + $this->assertEquals( + '1975-01-02 17:38.59', + $prm->getValue()->toDateTime() + ); + } +} +?> diff --git a/test/core/PrimitiveTimestampTest.class.php b/test/core/PrimitiveTimestampTest.class.php old mode 100644 new mode 100755 index 2dfe06d9f9..1e07f5e0c8 --- a/test/core/PrimitiveTimestampTest.class.php +++ b/test/core/PrimitiveTimestampTest.class.php @@ -20,11 +20,20 @@ public function testMarried() $this->assertFalse($prm->import($array)); $this->assertEquals($prm->getRawValue(), $array['test']); - + + $this->assertEmpty( + array_filter($prm->exportValue()) + ); + $array['test'][PrimitiveDate::YEAR] = '3456'; $this->assertTrue($prm->import($array)); $this->assertTrue($prm->getValue()->getYear() == 3456); + + $this->assertEquals( + $array['test'], + $prm->exportValue() + ); $array['test'][PrimitiveDate::YEAR] = '2006'; @@ -39,6 +48,7 @@ public function testMarried() public function testMarriedOptional() { + /** @var PrimitiveTimestamp $prm */ $prm = Primitive::timestamp('test')-> setComplex()-> diff --git a/test/core/SingletonTest.class.php b/test/core/SingletonTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/TimestampTZTest.class.php b/test/core/TimestampTZTest.class.php new file mode 100644 index 0000000000..71be3f0fac --- /dev/null +++ b/test/core/TimestampTZTest.class.php @@ -0,0 +1,66 @@ +assertEquals( + '2011-01-01 12:10:10+0300', + $someDate->toTimestamp('Europe/Moscow')->toString() + ); + + $this->assertEquals( + $someDate->toTimestamp('Europe/London')->toString(), + '2011-01-01 09:10:10+0000' + ); + + $moscowTime = TimestampTZ::create('2011-01-01 00:00:00 Europe/Moscow'); + $londonTime = TimestampTZ::create('2010-12-31 21:00:00 Europe/London'); + + $this->assertEquals(0, TimestampTZ::compare($moscowTime, $londonTime)); + + $moscowTime->modify('+ 1 second'); + $this->assertEquals(TimestampTZ::compare($moscowTime, $londonTime), 1); + $moscowTime->modify('- 2 second'); + $this->assertEquals(TimestampTZ::compare($moscowTime, $londonTime), -1); + + + $this->assertEquals( + $moscowTime->toTimestamp('Europe/Moscow')->toString(), + '2010-12-31 23:59:59+0300' + ); + } + + /** + * @group ff + */ + public function testDialect() + { + //setup + $someDate = TimestampTZ::create('2012-02-23 12:12:12 GMT'); + //expectation + $expectation = $someDate->toTimestamp()->toString(); + + //check + $this->assertEquals( + $someDate->toDialectString(ImaginaryDialect::me()), + $expectation + ); + } + + /** + * @group ff + */ + public function testSleeping() { + $time = TimestampTZ::create('2011-03-08 12:12:12 PST'); + $sleepedTime = unserialize(serialize($time)); + + $this->assertEquals(TimestampTZ::compare($time, $sleepedTime), 0); + } +} +?> diff --git a/test/core/TimestampTest.class.php b/test/core/TimestampTest.class.php old mode 100644 new mode 100755 index 32b6e27e8d..da3d49c9d5 --- a/test/core/TimestampTest.class.php +++ b/test/core/TimestampTest.class.php @@ -7,17 +7,17 @@ public function testNonEpoch() { $future = '4683-03-04'; $after = new Timestamp($future); - - $this->assertEquals($after->getDay(), '04'); - $this->assertEquals($after->getMonth(), '03'); - $this->assertEquals($after->getYear(), '4683'); + + $this->assertEquals('04', $after->getDay()); + $this->assertEquals('03', $after->getMonth()); + $this->assertEquals('4683', $after->getYear()); $past = '1234-04-03'; $before = new Timestamp($past); - - $this->assertEquals($before->getDay(), '03'); - $this->assertEquals($before->getMonth(), '04'); - $this->assertEquals($before->getYear(), '1234'); + + $this->assertEquals('03', $before->getDay()); + $this->assertEquals('04', $before->getMonth()); + $this->assertEquals('1234', $before->getYear()); $this->assertFalse($after->equals($before)); @@ -27,6 +27,36 @@ public function testNonEpoch() $time = ' 00:00.00'; $this->assertEquals($future.$time, $after->toDateTime()); $this->assertEquals($past.$time, $before->toDateTime()); + + $past = '1-04-03'; + $before = new Timestamp($past); + + $this->assertEquals('03', $before->getDay()); + $this->assertEquals('04', $before->getMonth()); + $this->assertEquals( + substr(date('Y', time()), 0, 2).'01', + $before->getYear() + ); + + $past = '14-01-02'; + $before = new Timestamp($past); + + $this->assertEquals('02', $before->getDay()); + $this->assertEquals('01', $before->getMonth()); + $this->assertEquals( + substr(date('Y', time()), 0, 2).'14', + $before->getYear() + ); + + $past = '113-01-02'; + $before = new Timestamp($past); + + $this->assertEquals('02', $before->getDay()); + $this->assertEquals('01', $before->getMonth()); + $this->assertEquals( + '0113', + $before->getYear() + ); } public function testInvalidTimestamp() @@ -123,6 +153,32 @@ public function testSleeping() $this->assertEquals($stamp->getMinute(), $unserializedStamp->getMinute()); $this->assertEquals($stamp->getSecond(), $unserializedStamp->getSecond()); + + $stamp = Timestamp::create('2039-01-05 12:14:05 Europe/Moscow'); + + $serializedStamp = serialize($stamp); + + $unserializedStamp = unserialize($serializedStamp); + + $this->assertEquals($stamp->getDay(), $unserializedStamp->getDay()); + $this->assertEquals($stamp->getMonth(), $unserializedStamp->getMonth()); + $this->assertEquals($stamp->getYear(), $unserializedStamp->getYear()); + + $this->assertEquals($stamp->getMinute(), $unserializedStamp->getMinute()); + $this->assertEquals($stamp->getSecond(), $unserializedStamp->getSecond()); + + $stamp = Timestamp::create('1899-12-31 16:07:45 Europe/London'); + + $serializedStamp = serialize($stamp); + + $unserializedStamp = unserialize($serializedStamp); + + $this->assertEquals($stamp->getDay(), $unserializedStamp->getDay()); + $this->assertEquals($stamp->getMonth(), $unserializedStamp->getMonth()); + $this->assertEquals($stamp->getYear(), $unserializedStamp->getYear()); + + $this->assertEquals($stamp->getMinute(), $unserializedStamp->getMinute()); + $this->assertEquals($stamp->getSecond(), $unserializedStamp->getSecond()); } } ?> \ No newline at end of file diff --git a/test/core/TruncateQueryTest.class.php b/test/core/TruncateQueryTest.class.php old mode 100644 new mode 100755 diff --git a/test/core/UnionTest.class.php b/test/core/UnionTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Base62UtilsTest.class.php b/test/main/Base62UtilsTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/ClassUtilsTest.class.php b/test/main/ClassUtilsTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/ComparatorTest.class.php b/test/main/ComparatorTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/CriteriaTest.class.php b/test/main/CriteriaTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/CrypterTest.class.php b/test/main/CrypterTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/CryptoTest.class.php b/test/main/CryptoTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/CsvTest.class.php b/test/main/CsvTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/DateUtilsTest.class.php b/test/main/DateUtilsTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/EntityProtoConvertersTest.class.php b/test/main/EntityProtoConvertersTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/EnumerationTest.class.php b/test/main/EnumerationTest.class.php index 1eccea51e5..f9441c60cb 100644 --- a/test/main/EnumerationTest.class.php +++ b/test/main/EnumerationTest.class.php @@ -14,6 +14,16 @@ public function testAnyId() ) ); + /* pass */ + } catch (MissingElementException $e) { + $this->fail($className); + } + } elseif(is_subclass_of($className, 'Enum')) { + try { + $enum = new $className( + ClassUtils::callStaticMethod($className.'::getAnyId') + ); + /* pass */ } catch (MissingElementException $e) { $this->fail($className); diff --git a/test/main/FeedReaderTest.class.php b/test/main/FeedReaderTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/FloatRangeTest.class.php b/test/main/FloatRangeTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/GenericUriTest.class.php b/test/main/GenericUriTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/GoogleChartTest.class.php b/test/main/GoogleChartTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/HeaderParserTest.class.php b/test/main/HeaderParserTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/HttpUtilsTest.class.php b/test/main/HttpUtilsTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Ip/IpAddressTest.class.php b/test/main/Ip/IpAddressTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Ip/IpBaseRangeTest.class.php b/test/main/Ip/IpBaseRangeTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Ip/IpNetworkTest.class.php b/test/main/Ip/IpNetworkTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Ip/IpUtilsTest.class.php b/test/main/Ip/IpUtilsTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/JsonViewTest.class.php b/test/main/JsonViewTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/MathTest.class.php b/test/main/MathTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/MathUtilsTest.class.php b/test/main/MathUtilsTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/MessagesTest.class.php b/test/main/MessagesTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/MimeMailTest.class.php b/test/main/MimeMailTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Net/CookieTest.class.php b/test/main/Net/CookieTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Net/MailTest.class.php b/test/main/Net/MailTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/OpenIdTest.class.php b/test/main/OpenIdTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/OqlSelectClauseTest.class.php b/test/main/OqlSelectClauseTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/OqlSelectTest.class.php b/test/main/OqlSelectTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/OqlTokenizerTest.class.php b/test/main/OqlTokenizerTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/OsqlDeleteTest.class.php b/test/main/OsqlDeleteTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/OsqlInsertTest.class.php b/test/main/OsqlInsertTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/OsqlReturningTest.class.php b/test/main/OsqlReturningTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/OsqlSelectTest.class.php b/test/main/OsqlSelectTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/PostgresDialectTest.class.php b/test/main/PostgresDialectTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/RangeTest.class.php b/test/main/RangeTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/TextUtilsTest.class.php b/test/main/TextUtilsTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Utils/AMQP/AMQPPeclTest.class.php b/test/main/Utils/AMQP/AMQPPeclTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Utils/ArrayUtilsTest.class.php b/test/main/Utils/ArrayUtilsTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Utils/FileUtilsTest.class.php b/test/main/Utils/FileUtilsTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Utils/Routers/RouterBaseRuleTest.class.php b/test/main/Utils/Routers/RouterBaseRuleTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Utils/Routers/RouterChainRuleTest.class.php b/test/main/Utils/Routers/RouterChainRuleTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Utils/Routers/RouterHostnameRuleTest.class.php b/test/main/Utils/Routers/RouterHostnameRuleTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Utils/Routers/RouterRegexpRuleTest.class.php b/test/main/Utils/Routers/RouterRegexpRuleTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Utils/Routers/RouterRewriteTest.class.php b/test/main/Utils/Routers/RouterRewriteTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Utils/Routers/RouterStaticRuleTest.class.php b/test/main/Utils/Routers/RouterStaticRuleTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Utils/Routers/RouterTransparentRuleTest.class.php b/test/main/Utils/Routers/RouterTransparentRuleTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Utils/SocketTest.class.php b/test/main/Utils/SocketTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Utils/TypesUtilsTest.class.php b/test/main/Utils/TypesUtilsTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/Utils/WebMoneyUtilsTest.class.php b/test/main/Utils/WebMoneyUtilsTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/ViewTest.class.php b/test/main/ViewTest.class.php old mode 100644 new mode 100755 diff --git a/test/main/data/directory/contents b/test/main/data/directory/contents old mode 100644 new mode 100755 diff --git a/test/main/data/directory/inner/textField b/test/main/data/directory/inner/textField old mode 100644 new mode 100755 diff --git a/test/main/data/directory/textField b/test/main/data/directory/textField old mode 100644 new mode 100755 diff --git a/test/main/data/feedReader/atom_v1_0.xml b/test/main/data/feedReader/atom_v1_0.xml old mode 100644 new mode 100755 diff --git a/test/main/data/feedReader/news.xml b/test/main/data/feedReader/news.xml old mode 100644 new mode 100755 diff --git a/test/main/data/feedReader/yandex_rss.xml b/test/main/data/feedReader/yandex_rss.xml old mode 100644 new mode 100755 diff --git a/test/main/data/mimeMail/encodedBody.txt b/test/main/data/mimeMail/encodedBody.txt old mode 100644 new mode 100755 diff --git a/test/main/data/mimeMail/headers.txt b/test/main/data/mimeMail/headers.txt old mode 100644 new mode 100755 diff --git a/test/main/data/mimeMail/message.html b/test/main/data/mimeMail/message.html old mode 100644 new mode 100755 diff --git a/test/main/data/mimeMail/picture.jpg b/test/main/data/mimeMail/picture.jpg old mode 100644 new mode 100755 diff --git a/test/main/data/unicode/utf16.txt b/test/main/data/unicode/utf16.txt old mode 100644 new mode 100755 diff --git a/test/main/data/unicode/utf16be.txt b/test/main/data/unicode/utf16be.txt old mode 100644 new mode 100755 diff --git a/test/main/data/unicode/utf16le.txt b/test/main/data/unicode/utf16le.txt old mode 100644 new mode 100755 diff --git a/test/main/data/unicode/utf8.txt b/test/main/data/unicode/utf8.txt old mode 100644 new mode 100755 diff --git a/test/main/data/urls/.._.._.._.._g.dump b/test/main/data/urls/.._.._.._.._g.dump old mode 100644 new mode 100755 diff --git a/test/main/data/urls/.dump b/test/main/data/urls/.dump old mode 100644 new mode 100755 diff --git a/test/main/data/urls/parser.dump b/test/main/data/urls/parser.dump old mode 100644 new mode 100755 diff --git a/test/main/data/views/testPartView.tpl.html b/test/main/data/views/testPartView.tpl.html old mode 100644 new mode 100755 diff --git a/test/main/data/views/testView.tpl.html b/test/main/data/views/testView.tpl.html old mode 100644 new mode 100755 diff --git a/test/main/data/views/testViewToString.tpl.html b/test/main/data/views/testViewToString.tpl.html old mode 100644 new mode 100755 diff --git a/test/meta/config.inc.php b/test/meta/config.inc.php old mode 100644 new mode 100755 diff --git a/test/meta/config.meta.xml b/test/meta/config.meta.xml index e4341b477b..ba98dc7840 100644 --- a/test/meta/config.meta.xml +++ b/test/meta/config.meta.xml @@ -153,6 +153,13 @@ + + + + + + + @@ -160,6 +167,7 @@ + diff --git a/test/misc/DAOTest.class.php b/test/misc/DAOTest.class.php old mode 100644 new mode 100755 index 14f8dacb8a..016ccb5a16 --- a/test/misc/DAOTest.class.php +++ b/test/misc/DAOTest.class.php @@ -1,6 +1,6 @@ getColumnByName('root_id')-> dropReference(); - + return parent::create(); } - + public function testSchema() { return $this->create()->drop(); } - + public function testData() { $this->create(); - + foreach (DBTestPool::me()->getPool() as $connector => $db) { DBPool::me()->setDefault($db); $this->fill(); - + $this->getSome(); // 41! Cache::me()->clean(); $this->getSome(); - + $this->nonIntegerIdentifier(); - + $this->racySave(); $this->binaryTest(); $this->lazyTest(); } - + $this->drop(); } - + public function testBoolean() { $this->create(); - + foreach (DBTestPool::me()->getPool() as $connector => $db) { DBPool::me()->setDefault($db); - + //creating moscow $moscow = TestCity::create()->setName('Moscow'); $moscow = $moscow->dao()->add($moscow); $moscowId = $moscow->getId(); /* @var $moscow TestCity */ - + //now moscow capital $moscow->dao()->merge($moscow->setCapital(true)); TestCity::dao()->dropIdentityMap(); - + Criteria::create(TestCity::dao())-> setSilent(false)-> add(Expression::isTrue('capital'))-> get(); TestCity::dao()->dropIdentityMap(); - + $moscow = Criteria::create(TestCity::dao())-> setSilent(false)-> add(Expression::isNull('large'))-> get(); TestCity::dao()->dropIdentityMap(); - + //now moscow large $moscow = $moscow->dao()->merge($moscow->setLarge(true)); - + TestCity::dao()->dropIdentityMap(); $moscow = TestCity::dao()->getById($moscowId); $this->assertTrue($moscow->getCapital()); $this->assertTrue($moscow->getLarge()); - + Criteria::create(TestCity::dao())-> setSilent(false)-> add(Expression::not(Expression::isFalse('large')))-> get(); TestCity::dao()->dropIdentityMap(); } - + $this->drop(); } - + public function testCriteria() { $this->create(); - + foreach (DBTestPool::me()->getPool() as $connector => $db) { DBPool::me()->setDefault($db); $this->fill(); - + $this->criteriaResult(); - + Cache::me()->clean(); } - + $this->deletedCount(); - + $this->drop(); } - + public function testUnified() { $this->create(); - + foreach (DBTestPool::me()->getPool() as $connector => $db) { DBPool::me()->setDefault($db); $this->fill(); - + $this->unified(); - + Cache::me()->clean(); } - + $this->deletedCount(); - + $this->drop(); } - + public function testCount() { $this->create(); - + foreach (DBTestPool::me()->getPool() as $connector => $db) { DBPool::me()->setDefault($db); - + $this->fill(); - + $count = TestUser::dao()->getTotalCount(); - + $this->assertGreaterThan(1, $count); - + $city = TestCity::create()-> setId(1); - + $newUser = TestUser::create()-> setCity($city)-> @@ -158,40 +158,40 @@ public function testCount() setRegistered( Timestamp::create(time()) ); - + TestUser::dao()->add($newUser); - + $newCount = TestUser::dao()->getTotalCount(); - + $this->assertEquals($count + 1, $newCount); } - + $this->drop(); } - + public function testGetByEmptyId() { $this->create(); - + $this->getByEmptyIdTest(0); $this->getByEmptyIdTest(null); $this->getByEmptyIdTest(''); $this->getByEmptyIdTest('0'); $this->getByEmptyIdTest(false); - + $empty = TestLazy::create(); - + $this->assertNull($empty->getCity()); $this->assertNull($empty->getCityOptional()); $this->assertNull($empty->getEnum()); - + $this->drop(); } - + public function deletedCount() { TestUser::dao()->dropById(1); - + try { TestUser::dao()->dropByIds(array(1, 2)); $this->fail(); @@ -199,17 +199,17 @@ public function deletedCount() // ok } } - + public function fill($assertions = true) { $moscow = TestCity::create()-> setName('Moscow'); - + $piter = TestCity::create()-> setName('Saint-Peterburg'); - + $mysqler = TestUser::create()-> setCity($moscow)-> @@ -224,9 +224,9 @@ public function fill($assertions = true) setRegistered( Timestamp::create(time())->modify('-1 day') ); - + $postgreser = clone $mysqler; - + $postgreser-> setCredentials( Credentials::create()-> @@ -235,23 +235,23 @@ public function fill($assertions = true) )-> setCity($piter)-> setUrl(HttpUrl::create()->parse('http://postgresql.org/')); - + $piter = TestCity::dao()->add($piter); $moscow = TestCity::dao()->add($moscow); - + if ($assertions) { $this->assertEquals($piter->getId(), 1); $this->assertEquals($moscow->getId(), 2); } - + $postgreser = TestUser::dao()->add($postgreser); - + for ($i = 0; $i < 10; $i++) { $encapsulant = TestEncapsulant::dao()->add( TestEncapsulant::create()-> setName($i) ); - + $encapsulant->getCities()-> fetch()-> setList( @@ -259,119 +259,119 @@ public function fill($assertions = true) )-> save(); } - + $mysqler = TestUser::dao()->add($mysqler); - + if ($assertions) { $this->assertEquals($postgreser->getId(), 1); $this->assertEquals($mysqler->getId(), 2); } - + if ($assertions) { // put them in cache now TestUser::dao()->dropIdentityMap(); - + TestUser::dao()->getById(1); TestUser::dao()->getById(2); - + $this->getListByIdsTest(); - + Cache::me()->clean(); - + $this->assertTrue( ($postgreser == TestUser::dao()->getById(1)) ); - + $this->assertTrue( ($mysqler == TestUser::dao()->getById(2)) ); } - + $firstClone = clone $postgreser; $secondClone = clone $mysqler; - + $firstCount = TestUser::dao()->dropById($postgreser->getId()); $secondCount = TestUser::dao()->dropByIds(array($mysqler->getId())); - + if ($assertions) { $this->assertEquals($firstCount, 1); $this->assertEquals($secondCount, 1); - + try { TestUser::dao()->getById(1); $this->fail(); } catch (ObjectNotFoundException $e) { /* pass */ } - + $result = Criteria::create(TestUser::dao())-> add(Expression::eq(1, 2))-> getResult(); - + $this->assertEquals($result->getCount(), 0); $this->assertEquals($result->getList(), array()); } - + TestUser::dao()->import($firstClone); TestUser::dao()->import($secondClone); - + if ($assertions) { // cache multi-get $this->getListByIdsTest(); $this->getListByIdsTest(); } } - + public function criteriaResult() { $queryResult = Criteria::create(TestCity::dao())->getResult(); $this->assertEquals(2, $queryResult->getCount()); } - + public function unified() { $user = TestUser::dao()->getById(1); - + $encapsulant = TestEncapsulant::dao()->getPlainList(); - + $collectionDao = $user->getEncapsulants(); - + $collectionDao->fetch()->setList($encapsulant); - + $collectionDao->save(); - + unset($collectionDao); - + // fetch $encapsulantsList = $user->getEncapsulants()->getList(); - + $piter = TestCity::dao()->getById(1); $moscow = TestCity::dao()->getById(2); - + for ($i = 0; $i < 10; $i++) { $this->assertEquals($encapsulantsList[$i]->getId(), $i + 1); $this->assertEquals($encapsulantsList[$i]->getName(), $i); - + $cityList = $encapsulantsList[$i]->getCities()->getList(); - + $this->assertEquals($cityList[0], $piter); $this->assertEquals($cityList[1], $moscow); } - + unset($encapsulantsList); - + // lazy fetch $encapsulantsList = $user->getEncapsulants(true)->getList(); - + for ($i = 1; $i < 11; $i++) $this->assertEquals($encapsulantsList[$i], $i); - + // count $user->getEncapsulants()->clean(); - + $this->assertEquals($user->getEncapsulants()->getCount(), 10); - + $criteria = Criteria::create(TestEncapsulant::dao())-> add( Expression::in( @@ -379,64 +379,76 @@ public function unified() array($piter->getId(), $moscow->getId()) ) ); - + $user->getEncapsulants()->setCriteria($criteria); - + $this->assertEquals($user->getEncapsulants()->getCount(), 20); - + // distinct count $user->getEncapsulants()->clean(); - + $user->getEncapsulants()->setCriteria( $criteria-> setDistinct(true) ); - + if (DBPool::me()->getLink() instanceof SQLite) // TODO: sqlite does not support such queries yet return null; - + $this->assertEquals($user->getEncapsulants()->getCount(), 10); + + // unified container __clone + $dao = $user->getEncapsulants(); + $dao->setCriteria(Criteria::create()); // empty criteria + $cloneDao = clone $dao; + $cloneDao->setCriteria( // criteria with 1 expression + Criteria::create() + ->add( + Expression::gt('id',0) + ) + ); + $this->assertNotEquals($dao, $cloneDao); // they should be different } - + public function testWorkingWithCache() { $this->create(); - + foreach (DBTestPool::me()->getPool() as $connector => $db) { DBPool::me()->setDefault($db); - + $item = TestItem::create()-> setName('testItem1'); - + TestItem::dao()->add($item); - + $encapsulant = TestEncapsulant::create()-> setName('testEncapsulant1'); - + TestEncapsulant::dao()->add($encapsulant); - + $subItem1 = TestSubItem::create()-> setName('testSubItem1')-> setEncapsulant($encapsulant)-> setItem($item); - + $subItem2 = TestSubItem::create()-> setName('testSubItem2')-> setEncapsulant($encapsulant)-> setItem($item); - + TestSubItem::dao()->add($subItem1); TestSubItem::dao()->add($subItem2); - + $items = Criteria::create(TestItem::dao())-> getList(); - + foreach ($items as $item) { foreach ($item->getSubItems()->getList() as $subItem) { $this->assertEquals( @@ -445,22 +457,22 @@ public function testWorkingWithCache() ); } } - + $encapsulant = TestEncapsulant::dao()->getById(1); - + $encapsulant->setName('testEncapsulant1_changed'); - + TestEncapsulant::dao()->save($encapsulant); - + // drop identityMap TestEncapsulant::dao()->dropIdentityMap(); TestSubItem::dao()->dropIdentityMap(); TestItem::dao()->dropIdentityMap(); - + $items = Criteria::create(TestItem::dao())-> getList(); - + foreach ($items as $item) { foreach ($item->getSubItems()->getList() as $subItem) { $this->assertEquals( @@ -469,28 +481,28 @@ public function testWorkingWithCache() ); } } - + // drop identityMap TestEncapsulant::dao()->dropIdentityMap(); TestSubItem::dao()->dropIdentityMap(); TestItem::dao()->dropIdentityMap(); - + $subItem = TestSubItem::dao()->getById(1); - + $this->assertEquals( $subItem->getEncapsulant()->getName(), 'testEncapsulant1_changed' ); - + // drop identityMap TestEncapsulant::dao()->dropIdentityMap(); TestSubItem::dao()->dropIdentityMap(); TestItem::dao()->dropIdentityMap(); - + $subItems = Criteria::create(TestSubItem::dao())-> getList(); - + foreach ($subItems as $subItem) { $this->assertEquals( $subItem->getEncapsulant()->getName(), @@ -498,10 +510,10 @@ public function testWorkingWithCache() ); } } - + $this->drop(); } - + /** * Install hstore * /usr/share/postgresql/contrib # cat hstore.sql | psql -U pgsql -d onphp @@ -509,13 +521,13 @@ public function testWorkingWithCache() public function testHstore() { $this->create(); - + $properties = array( 'age' => '23', 'weight' => 80, 'comment' => null, ); - + $user = TestUser::create()-> setCity( @@ -534,26 +546,26 @@ public function testHstore() Timestamp::create(time())->modify('-1 day') )-> setProperties(Hstore::make($properties)); - + $moscow = TestCity::dao()->add($moscow); - + $user = TestUser::dao()->add($user); - + Cache::me()->clean(); TestUser::dao()->dropIdentityMap(); - + $user = TestUser::dao()->getById('1'); - + $this->assertInstanceOf('Hstore', $user->getProperties()); - + $this->assertEquals( $properties, $user->getProperties()->getList() ); - - + + $form = TestUser::proto()->makeForm(); - + $form->get('properties')-> setFormMapping( array( @@ -562,93 +574,93 @@ public function testHstore() Primitive::string('comment'), ) ); - + $form->import( array('id' => $user->getId()) ); - + $this->assertNotNull($form->getValue('id')); - + $object = $user; - + FormUtils::object2form($object, $form); - + $this->assertInstanceOf('Hstore', $form->getValue('properties')); - + $this->assertEquals( array_filter($properties), $form->getValue('properties')->getList() ); - + $subform = $form->get('properties')->getInnerForm(); - + $this->assertEquals( $subform->getValue('age'), '23' ); - + $this->assertEquals( $subform->getValue('weight'), 80 ); - + $this->assertNull( $subform->getValue('comment') ); - + $user = new TestUser(); - + FormUtils::form2object($form, $user, false); - + $this->assertEquals( $user->getProperties()->getList(), array_filter($properties) ); - + $this->drop(); } - + /** * @see http://lists.shadanakar.org/onphp-dev-ru/0811/0774.html **/ public function testRecursiveContainers() { $this->markTestSkipped('wontfix'); - + $this->create(); - + TestObject::dao()->import( TestObject::create()-> setId(1)-> setName('test object') ); - + TestType::dao()->import( TestType::create()-> setId(1)-> setName('test type') ); - + $type = TestType::dao()->getById(1); - + $type->getObjects()->fetch()->setList( array(TestObject::dao()->getById(1)) )-> save(); - + $object = TestObject::dao()->getById(1); - + TestObject::dao()->save($object->setName('test object modified')); - + $list = $type->getObjects()->getList(); - + $modifiedObject = TestObject::dao()->getById(1); - + $this->assertEquals($list[0], $modifiedObject); - + $this->drop(); } - + public function testRecursionObjects() { $this->create(); @@ -698,30 +710,30 @@ public function nonIntegerIdentifier() { $id = 'non-integer-one'; $binaryData = "\0!bbq!\0"; - + $bin = TestBinaryStuff::create()-> setId($id)-> setData($binaryData); - + try { TestBinaryStuff::dao()->import($bin); } catch (DatabaseException $e) { return $this->fail(); } - + Cache::me()->clean(); - + $prm = Primitive::prototypedIdentifier('TestBinaryStuff', 'id'); - + $this->assertTrue($prm->import(array('id' => $id))); $this->assertSame($prm->getValue()->getId(), $id); - + $binLoaded = TestBinaryStuff::dao()->getById($id); $this->assertEquals($binLoaded, $bin); $this->assertEquals($binLoaded->getData(), $binaryData); $this->assertEquals(TestBinaryStuff::dao()->dropById($id), 1); - + $integerIdPrimitive = Primitive::prototypedIdentifier('TestUser'); try { $integerIdPrimitive->import(array('id' => 'string-instead-of-integer')); @@ -729,17 +741,17 @@ public function nonIntegerIdentifier() return $this->fail(); } } - + public function testIpAddressProperty() { $this->create(); - + $city = TestCity::create()-> setName('Khimki'); - + TestCity::dao()->add($city); - + $userWithIp = TestUser::create()-> setCredentials( @@ -751,13 +763,13 @@ public function testIpAddressProperty() setRegistered(Timestamp::makeNow())-> setCity($city)-> setIp(IpAddress::create('127.0.0.1')); - + TestUser::dao()->add($userWithIp); - + $this->assertTrue($userWithIp->getId() >= 1); - + $this->assertTrue($userWithIp->getIp() instanceof IpAddress); - + $plainIp = DBPool::me()->getByDao(TestUser::dao())-> queryColumn( @@ -765,24 +777,24 @@ public function testIpAddressProperty() from(TestUser::dao()->getTable())-> where(Expression::eq('id', $userWithIp->getId())) ); - + $this->assertEquals($plainIp[0], $userWithIp->getIp()->toString()); - + $count = Criteria::create(TestUser::dao())-> add(Expression::eq('ip', IpAddress::create('127.0.0.1')))-> addProjection(Projection::count('*', 'count'))-> getCustom('count'); - + $this->assertEquals($count, 1); - + $this->drop(); } - + public function testIpRangeProperty() { $this->create(); - + $akado = TestInternetProvider::create()-> setName('Akada')-> @@ -792,21 +804,21 @@ public function testIpRangeProperty() IpAddress::create('192.168.1.42') ) ); - + TestInternetProvider::dao()-> add($akado); - + $plainRange = Criteria::create(TestInternetProvider::dao())-> addProjection(Projection::property('range'))-> add(Expression::eq('name', 'Akada'))-> getCustom(); - + $this->assertEquals( $plainRange['range'], '192.168.1.1-192.168.1.42' ); - + TestInternetProvider::dao()-> add( TestInternetProvider::create()-> @@ -815,28 +827,28 @@ public function testIpRangeProperty() IpRange::create('192.168.2.0/24') ) ); - + $list = Criteria::create(TestInternetProvider::dao())-> addOrder('id')-> getList(); - + $this->assertEquals(count($list), 2); - + $this->drop(); } - + public function testLazy() { $this->create(); - + $parent = TestParentObject::create(); $child = TestChildObject::create()->setParent($parent); - + $parent->dao()->add($parent); - + $child->dao()->add($child); - + $this->assertEquals( $parent->getId(), Criteria::create(TestChildObject::dao())-> @@ -846,10 +858,10 @@ public function testLazy() add(Expression::eq('id', $child->getId()))-> getCustom('parentId') ); - + $this->drop(); } - + protected function getSome() { for ($i = 1; $i < 3; ++$i) { @@ -860,83 +872,83 @@ protected function getSome() == TestUser::dao()->getById($i) ); } - + $this->assertEquals( count(TestUser::dao()->getPlainList()), count(TestCity::dao()->getPlainList()) ); } - + private function racySave() { $lost = TestCity::create()-> setId(424242)-> setName('inexistant city'); - + try { TestCity::dao()->save($lost); - + $this->fail(); } catch (WrongStateException $e) { /* pass */ } } - + private function binaryTest() { $data = null; - + for ($i = 0; $i < 256; ++$i) $data .= chr($i); - + $id = sha1('all sessions are evil'); - + $stuff = TestBinaryStuff::create()-> setId($id)-> setData($data); - + $stuff = $stuff->dao()->import($stuff); - + Cache::me()->clean(); - + $this->assertEquals( TestBinaryStuff::dao()->getById($id)->getData(), $data ); - + TestBinaryStuff::dao()->dropById($id); } - + private function getListByIdsTest() { $first = TestUser::dao()->getById(1); - + TestUser::dao()->dropIdentityMap(); - + $list = TestUser::dao()->getListByIds(array(1, 3, 2, 1, 1, 1)); - + $this->assertEquals(count($list), 5); - + $this->assertEquals($list[0]->getId(), 1); $this->assertEquals($list[1]->getId(), 2); $this->assertEquals($list[2]->getId(), 1); $this->assertEquals($list[3]->getId(), 1); $this->assertEquals($list[4]->getId(), 1); - + $this->assertEquals($list[0], $first); - + $this->assertEquals( array(), TestUser::dao()->getListByIds(array(42, 42, 1738)) ); } - + private function lazyTest() { $city = TestCity::dao()->getById(1); - + $object = TestLazy::dao()->add( TestLazy::create()-> setCity($city)-> @@ -945,18 +957,18 @@ private function lazyTest() new ImageType(ImageType::getAnyId()) ) ); - + Cache::me()->clean(); - + $form = TestLazy::proto()->makeForm(); $form->import( array('id' => $object->getId()) ); - + $this->assertNotNull($form->getValue('id')); - + FormUtils::object2form($object, $form); - + foreach ($object->proto()->getPropertyList() as $name => $property) { if ( $property->getRelationId() == MetaRelation::ONE_TO_ONE @@ -969,7 +981,7 @@ private function lazyTest() } } } - + private function getByEmptyIdTest($id) { try { diff --git a/test/misc/DBTestPool.class.php b/test/misc/DBTestPool.class.php old mode 100644 new mode 100755 diff --git a/test/misc/ServerVarUtils.class.php b/test/misc/ServerVarUtils.class.php old mode 100644 new mode 100755 diff --git a/test/misc/TestCase.class.php b/test/misc/TestCase.class.php old mode 100644 new mode 100755 diff --git a/test/misc/TestTables.class.php b/test/misc/TestTables.class.php old mode 100644 new mode 100755 index 6befdf2dd3..69bcc39a62 --- a/test/misc/TestTables.class.php +++ b/test/misc/TestTables.class.php @@ -1,18 +1,18 @@ schema = $schema; - + // in case of unclean shutdown of previous tests foreach (DBTestPool::me()->getPool() as $name => $db) { foreach ($this->schema->getTableNames() as $name) { @@ -25,7 +25,7 @@ public function __construct() } catch (DatabaseException $e) { // ok } - + if ($db->hasSequences()) { foreach ( $this->schema->getTableByName($name)->getColumns() @@ -34,7 +34,7 @@ public function __construct() { try { if ($column->isAutoincrement()) - $db->queryRaw("DROP SEQUENCE {$name}_id;"); + $db->queryRaw("DROP SEQUENCE {$name}_id CASCADE;"); } catch (DatabaseException $e) { // ok } @@ -43,24 +43,30 @@ public function __construct() } } } - + public function create() { $pool = DBTestPool::me()->getPool(); - + foreach ($pool as $name => $db) { + try { + $this->drop(); + } catch(DatabaseException $e) { + // ok + } + foreach ($this->schema->getTables() as $name => $table) { $db->queryRaw($table->toDialectString($db->getDialect())); } } - + return $this; } - + public function drop() { $pool = DBTestPool::me()->getPool(); - + foreach ($pool as $name => $db) { foreach ($this->schema->getTableNames() as $name) { $db->queryRaw( @@ -68,19 +74,19 @@ public function drop() $db->getDialect() ) ); - + if ($db->hasSequences()) { foreach ( $this->schema->getTableByName($name)->getColumns() as $columnName => $column) { if ($column->isAutoincrement()) - $db->queryRaw("DROP SEQUENCE {$name}_id;"); + $db->queryRaw("DROP SEQUENCE {$name}_id CASCADE;"); } } } } - + return $this; }