From 1ba40e35d67bb36d71cc292707858c3f211668ba Mon Sep 17 00:00:00 2001
From: Jared Hancock <jared@osticket.com>
Date: Thu, 17 Mar 2016 18:22:12 -0500
Subject: [PATCH] orm: Migrate create functionality to the constructor

Previously, the create method was used to create a new instance of an orm
model (which would later result in an INSERT when persisted); however, some
classes require or utilize varying parameters to their create() method,
which PHP7 considers an error. Methods in subclasses must be defined with a
calling signature compatible with the parent class.

This patch shifts the concept of model creation to the constructor. Now, the
constructor of ORM models is required to be compatible with that of
ModelBase class. Now that most models do not define a constructor, this is
much easier to control, and much more logical.

Also, remove an issue where assignments on a relationship field to an
instance of a super class of the foreign model would raise an error. This
was previously addressed by re-classing the instance in the
ModelInstanceManager::getOrBuild(); however that design would create
multiple instances of the same object in memory, which defeats one of the
primary design concepts of the ORM. This patch addresses the issue by
allowing super-classes of the declared foreign model in relationship
assignments.
---
 include/ajax.note.php                         |  16 +-
 include/api.tickets.php                       |   2 +-
 include/class.attachment.php                  |   4 +-
 include/class.canned.php                      |   2 +-
 include/class.category.php                    |   2 +-
 include/class.collaborator.php                |   2 +-
 include/class.dept.php                        |   3 +-
 include/class.draft.php                       |   2 +-
 include/class.dynamic_forms.php               |   8 +-
 include/class.email.php                       |   2 +-
 include/class.faq.php                         |   2 +-
 include/class.file.php                        |   8 +-
 include/class.filter.php                      |   2 +-
 include/class.forms.php                       |   2 +-
 include/class.list.php                        |   6 +-
 include/class.lock.php                        |   2 +-
 include/class.mailfetch.php                   |   2 +-
 include/class.organization.php                |  10 +-
 include/class.orm.php                         | 144 +++++++++++-------
 include/class.page.php                        |   2 +-
 include/class.role.php                        |   2 +-
 include/class.search.php                      |   2 +-
 include/class.sequence.php                    |   5 +-
 include/class.sla.php                         |   2 +-
 include/class.staff.php                       |   6 +-
 include/class.task.php                        |   2 +-
 include/class.team.php                        |   4 +-
 include/class.thread.php                      |  18 +--
 include/class.ticket.php                      |   6 +-
 include/class.topic.php                       |   7 +-
 include/class.translation.php                 |   2 +-
 include/class.upgrader.php                    |   2 +-
 include/class.user.php                        |  17 +--
 .../streams/core/15b30765-dd0022fb.task.php   |   2 +-
 .../streams/core/934954de-f1ccd3bb.task.php   |   2 +-
 open.php                                      |   2 +-
 setup/inc/class.installer.php                 |   2 +-
 37 files changed, 162 insertions(+), 144 deletions(-)

diff --git a/include/ajax.note.php b/include/ajax.note.php
index 8e179ab16..8980a5611 100644
--- a/include/ajax.note.php
+++ b/include/ajax.note.php
@@ -54,14 +54,14 @@ class NoteAjaxAPI extends AjaxController {
             Http::response(403, "Login required");
         elseif (!isset($_POST['note']) || !$_POST['note'])
             Http::response(422, "Send `note` parameter");
-        elseif (!($note = QuickNote::create(array(
-                'staff_id' => $thisstaff->getId(),
-                'body' => Format::sanitize($_POST['note']),
-                'created' => new SqlFunction('NOW'),
-                'ext_id' => $ext_id,
-                ))))
-            Http::response(500, "Unable to create new note");
-        elseif (!$note->save(true))
+
+        $note = new QuickNote(array(
+            'staff_id' => $thisstaff->getId(),
+            'body' => Format::sanitize($_POST['note']),
+            'created' => new SqlFunction('NOW'),
+            'ext_id' => $ext_id,
+        ));
+        if (!$note->save(true))
             Http::response(500, "Unable to create new note");
 
         $show_options = true;
diff --git a/include/api.tickets.php b/include/api.tickets.php
index e762fb18a..e2ee066ae 100644
--- a/include/api.tickets.php
+++ b/include/api.tickets.php
@@ -129,7 +129,7 @@ class TicketApiController extends ApiController {
         # Create the ticket with the data (attempt to anyway)
         $errors = array();
 
-        $ticket = Ticket::create2($data, $errors, $data['source'], $autorespond, $alert);
+        $ticket = Ticket::create($data, $errors, $data['source'], $autorespond, $alert);
         # Return errors (?)
         if (count($errors)) {
             if(isset($errors['errno']) && $errors['errno'] == 403)
diff --git a/include/class.attachment.php b/include/class.attachment.php
index 95e2fe242..15f3fd9b1 100644
--- a/include/class.attachment.php
+++ b/include/class.attachment.php
@@ -130,14 +130,14 @@ extends InstrumentedList {
                 $fileId = $file['id'];
             elseif (isset($file['tmp_name']) && ($F = AttachmentFile::upload($file)))
                 $fileId = $F->getId();
-            elseif ($F = AttachmentFile::createFile($file))
+            elseif ($F = AttachmentFile::create($file))
                 $fileId = $F->getId();
             else
                 continue;
 
             $_inline = isset($file['inline']) ? $file['inline'] : $inline;
 
-            $att = $this->add(Attachment::create(array(
+            $att = $this->add(new Attachment(array(
                 'file_id' => $fileId,
                 'inline' => $_inline ? 1 : 0,
             )));
diff --git a/include/class.canned.php b/include/class.canned.php
index 6bd2e99d0..bb126d740 100644
--- a/include/class.canned.php
+++ b/include/class.canned.php
@@ -207,7 +207,7 @@ extends VerySimpleModel {
     /*** Static functions ***/
 
     static function create($vars=false) {
-        $faq = parent::create($vars);
+        $faq = new static($vars);
         $faq->created = SqlFunction::NOW();
         return $faq;
     }
diff --git a/include/class.category.php b/include/class.category.php
index d786e5ee6..d8cbb72f2 100644
--- a/include/class.category.php
+++ b/include/class.category.php
@@ -244,7 +244,7 @@ class Category extends VerySimpleModel {
     }
 
     static function create($vars=false) {
-        $category = parent::create($vars);
+        $category = new static($vars);
         $category->created = SqlFunction::NOW();
         return $category;
     }
diff --git a/include/class.collaborator.php b/include/class.collaborator.php
index b51fbe48c..2d73efbfd 100644
--- a/include/class.collaborator.php
+++ b/include/class.collaborator.php
@@ -115,7 +115,7 @@ implements EmailContact, ITicketUser {
     }
 
     static function create($vars=false) {
-        $inst = parent::create($vars);
+        $inst = new static($vars);
         $inst->created = SqlFunction::NOW();
         return $inst;
     }
diff --git a/include/class.dept.php b/include/class.dept.php
index 495380b91..397df89c4 100644
--- a/include/class.dept.php
+++ b/include/class.dept.php
@@ -558,9 +558,8 @@ implements TemplateVariable {
     }
 
     static function create($vars=false, &$errors=array()) {
-        $dept = parent::create($vars);
+        $dept = new static($vars);
         $dept->created = SqlFunction::NOW();
-
         return $dept;
     }
 
diff --git a/include/class.draft.php b/include/class.draft.php
index a12fbf292..d123ff693 100644
--- a/include/class.draft.php
+++ b/include/class.draft.php
@@ -146,7 +146,7 @@ class Draft extends VerySimpleModel {
 
         $vars['created'] = SqlFunction::NOW();
         $vars['staff_id'] = self::getCurrentUserId();
-        $draft = parent::create($vars);
+        $draft = new static($vars);
 
         // Cloned attachments ...
         if (false && $attachments && is_array($attachments))
diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index bb1bce0ad..86e1d51d6 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -216,7 +216,7 @@ class DynamicForm extends VerySimpleModel {
     }
 
     static function create($ht=false) {
-        $inst = parent::create($ht);
+        $inst = new static($ht);
         $inst->set('created', new SqlFunction('NOW'));
         if (isset($ht['fields'])) {
             $inst->save();
@@ -896,7 +896,7 @@ class DynamicFormField extends VerySimpleModel {
     }
 
     static function create($ht=false) {
-        $inst = parent::create($ht);
+        $inst = new static($ht);
         $inst->set('created', new SqlFunction('NOW'));
         if (isset($ht['configuration']))
             $inst->configuration = JsonDataEncoder::encode($ht['configuration']);
@@ -1288,7 +1288,7 @@ class DynamicFormEntry extends VerySimpleModel {
     }
 
     static function create($ht=false, $data=null) {
-        $inst = parent::create($ht);
+        $inst = new static($ht);
         $inst->set('created', new SqlFunction('NOW'));
         if ($data)
             $inst->setSource($data);
@@ -1297,7 +1297,7 @@ class DynamicFormEntry extends VerySimpleModel {
                 continue;
             if (!$impl->hasData() || !$impl->isStorable())
                 continue;
-            $a = DynamicFormEntryAnswer::create(
+            $a = new DynamicFormEntryAnswer(
                 array('field'=>$field, 'entry'=>$inst));
             $a->field->setAnswer($a);
             $inst->answers->add($a);
diff --git a/include/class.email.php b/include/class.email.php
index 92348241c..e0bdb64bd 100644
--- a/include/class.email.php
+++ b/include/class.email.php
@@ -224,7 +224,7 @@ class Email extends VerySimpleModel {
     }
 
     static function create($vars=false) {
-        $inst = parent::create($vars);
+        $inst = new static($vars);
         $inst->created = SqlFunction::NOW();
         return $inst;
     }
diff --git a/include/class.faq.php b/include/class.faq.php
index 372ee0f0d..027b25ef0 100644
--- a/include/class.faq.php
+++ b/include/class.faq.php
@@ -338,7 +338,7 @@ class FAQ extends VerySimpleModel {
     }
 
     static function create($vars=false) {
-        $faq = parent::create($vars);
+        $faq = new static($vars);
         $faq->created = SqlFunction::NOW();
         return $faq;
     }
diff --git a/include/class.file.php b/include/class.file.php
index 6439f798d..a7b8eb648 100644
--- a/include/class.file.php
+++ b/include/class.file.php
@@ -293,7 +293,7 @@ class AttachmentFile extends VerySimpleModel {
                     'tmp_name'=>$file['tmp_name'],
                     );
 
-        return static::createFile($info, $ft, $deduplicate);
+        return static::create($info, $ft, $deduplicate);
     }
 
     static function uploadLogo($file, &$error, $aspect_ratio=2) {
@@ -327,7 +327,7 @@ class AttachmentFile extends VerySimpleModel {
         return false;
     }
 
-    static function createFile(&$file, $ft='T', $deduplicate=true) {
+    static function create(&$file, $ft='T', $deduplicate=true) {
         if (isset($file['encoding'])) {
             switch ($file['encoding']) {
             case 'base64':
@@ -380,7 +380,7 @@ class AttachmentFile extends VerySimpleModel {
             $file['type'] = 'application/octet-stream';
 
 
-        $f = static::create(array(
+        $f = new static(array(
             'type' => strtolower($file['type']),
             'name' => $file['name'],
             'key' => $file['key'],
@@ -445,7 +445,7 @@ class AttachmentFile extends VerySimpleModel {
     }
 
     static function __create($file, &$errors) {
-        return static::createFile($file);
+        return static::create($file);
     }
 
     /**
diff --git a/include/class.filter.php b/include/class.filter.php
index 9a31e510f..20bac5ce5 100644
--- a/include/class.filter.php
+++ b/include/class.filter.php
@@ -530,7 +530,7 @@ class Filter {
 
             switch ($action) {
             case 'N': # new filter action
-                $I = FilterAction::create(array(
+                $I = new FilterAction(array(
                     'type'=>$info,
                     'filter_id'=>$id,
                     'sort' => (int) $sort,
diff --git a/include/class.forms.php b/include/class.forms.php
index 02e918575..479d5f2ba 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -2651,7 +2651,7 @@ class FileUploadField extends FormField {
         if ($file['size'] > $config['size'])
             throw new FileUploadError(__('File size is too large'));
 
-        if (!$F = AttachmentFile::createFile($file))
+        if (!$F = AttachmentFile::create($file))
             throw new FileUploadError(__('Unable to save file'));
 
         return $F;
diff --git a/include/class.list.php b/include/class.list.php
index a60925874..84322f700 100644
--- a/include/class.list.php
+++ b/include/class.list.php
@@ -478,7 +478,7 @@ class DynamicList extends VerySimpleModel implements CustomList {
             $ht['configuration'] = JsonDataEncoder::encode($ht['configuration']);
         }
 
-        $inst = parent::create($ht);
+        $inst = new static($ht);
         $inst->set('created', new SqlFunction('NOW'));
 
         if (isset($ht['properties'])) {
@@ -798,7 +798,7 @@ class DynamicListItem extends VerySimpleModel implements CustomListItem {
         if (isset($ht['properties']) && is_array($ht['properties']))
             $ht['properties'] = JsonDataEncoder::encode($ht['properties']);
 
-        $inst = parent::create($ht);
+        $inst = new static($ht);
 
         // Auto-config properties if any
         if ($ht['configuration'] && is_array($ht['configuration'])) {
@@ -1398,7 +1398,7 @@ implements CustomListItem, TemplateVariable {
 
         $ht['created'] = new SqlFunction('NOW');
 
-        return  parent::create($ht);
+        return new static($ht);
     }
 
     static function lookup($var, $list=null) {
diff --git a/include/class.lock.php b/include/class.lock.php
index 427ecbbe9..c0e8c2823 100644
--- a/include/class.lock.php
+++ b/include/class.lock.php
@@ -115,7 +115,7 @@ class Lock extends VerySimpleModel {
             return null;
 
         // Create the new lock.
-        $lock = parent::create(array(
+        $lock = new static(array(
             'created' => SqlFunction::NOW(),
             'staff_id' => $staffId,
             'expire' => SqlExpression::plus(
diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php
index 9410c95d0..325300f7b 100644
--- a/include/class.mailfetch.php
+++ b/include/class.mailfetch.php
@@ -792,7 +792,7 @@ class MailFetcher {
             // NOTE: This might not be a "ticket"
             $ticket = $thread->getObject();
         }
-        elseif (($ticket=Ticket::create2($vars, $errors, 'Email'))) {
+        elseif (($ticket=Ticket::create($vars, $errors, 'Email'))) {
             $message = $ticket->getLastMessage();
         }
         else {
diff --git a/include/class.organization.php b/include/class.organization.php
index aa3690bee..669854350 100644
--- a/include/class.organization.php
+++ b/include/class.organization.php
@@ -448,8 +448,8 @@ implements TemplateVariable {
 
     static function fromVars($vars) {
 
-        if (!($org = Organization::lookup(array('name' => $vars['name'])))) {
-            $org = Organization::create(array(
+        if (!($org = static::lookup(array('name' => $vars['name'])))) {
+            $org = static::create(array(
                 'name' => $vars['name'],
                 'updated' => new SqlFunction('NOW'),
             ));
@@ -474,7 +474,7 @@ implements TemplateVariable {
         // Make sure the name is not in-use
         if (($field=$form->getField('name'))
                 && $field->getClean()
-                && Organization::lookup(array('name' => $field->getClean()))) {
+                && static::lookup(array('name' => $field->getClean()))) {
             $field->addError(__('Organization with the same name already exists'));
             $valid = false;
         }
@@ -483,7 +483,7 @@ implements TemplateVariable {
     }
 
     static function create($vars=false) {
-        $org = parent::create($vars);
+        $org = new static($vars);
 
         $org->created = new SqlFunction('NOW');
         $org->setStatus(self::SHARE_PRIMARY_CONTACT);
@@ -493,7 +493,7 @@ implements TemplateVariable {
     // Custom create called by installer/upgrader to load initial data
     static function __create($ht, &$error=false) {
 
-        $org = Organization::create($ht);
+        $org = static::create($ht);
         // Add dynamic data (if any)
         if ($ht['fields']) {
             $org->save(true);
diff --git a/include/class.orm.php b/include/class.orm.php
index 3c381d121..cbb73ee29 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -32,7 +32,7 @@ class InconsistentModelException extends OrmException {
  * name, default sorting information, database fields, etc.
  *
  * This class is constructed and built automatically from the model's
- * ::_inspect method using a class's ::$meta array.
+ * ::getMeta() method using a class's ::$meta array.
  */
 class ModelMeta implements ArrayAccess {
 
@@ -48,17 +48,24 @@ class ModelMeta implements ArrayAccess {
     static $model_cache;
 
     var $model;
+    var $meta = array();
+    var $new;
+    var $subclasses = array();
 
     function __construct($model) {
         $this->model = $model;
 
         // Merge ModelMeta from parent model (if inherited)
         $parent = get_parent_class($this->model);
+        $meta = $model::$meta;
+        if ($model::$meta instanceof self)
+            $meta = $meta->meta;
         if (is_subclass_of($parent, 'VerySimpleModel')) {
-            $meta = $parent::getMeta()->extend($model::$meta);
+            $this->parent = $parent::getMeta();
+            $meta = $this->parent->extend($this, $meta);
         }
         else {
-            $meta = $model::$meta + self::$base;
+            $meta = $meta + self::$base;
         }
 
         if (!$meta['view']) {
@@ -85,13 +92,30 @@ class ModelMeta implements ArrayAccess {
                 $meta['foreign_keys'][$j['local']] = $field;
         }
         unset($j);
-        $this->base = $meta;
+        $this->meta = $meta;
     }
 
-    function extend($meta) {
-        if ($meta instanceof self)
-            $meta = $meta->base;
-        return $meta + $this->base + self::$base;
+    function extend(ModelMeta $child, $meta) {
+        $this->subclasses[$child->model] = $child;
+        return $meta + $this->meta + self::$base;
+    }
+
+    function isSuperClassOf($model) {
+        if (isset($this->subclasses[$model]))
+            return true;
+        foreach ($this->subclasses as $M=>$meta)
+            if ($meta->isSuperClassOf($M))
+                return true;
+    }
+
+    function isSubclassOf($model) {
+        if (!isset($this->parent))
+            return false;
+
+        if ($this->parent->model === $model)
+            return true;
+
+        return $this->parent->isSubclassOf($model);
     }
 
     /**
@@ -159,20 +183,20 @@ class ModelMeta implements ArrayAccess {
     }
 
     function addJoin($name, array $join) {
-        $this->base['joins'][$name] = $join;
-        $this->processJoin($this->base['joins'][$name]);
+        $this->meta['joins'][$name] = $join;
+        $this->processJoin($this->meta['joins'][$name]);
     }
 
     function offsetGet($field) {
-        if (!isset($this->base[$field]))
+        if (!isset($this->meta[$field]))
             $this->setupLazy($field);
-        return $this->base[$field];
+        return $this->meta[$field];
     }
     function offsetSet($field, $what) {
-        $this->base[$field] = $what;
+        $this->meta[$field] = $what;
     }
     function offsetExists($field) {
-        return isset($this->base[$field]);
+        return isset($this->meta[$field]);
     }
     function offsetUnset($field) {
         throw new Exception('Model MetaData is immutable');
@@ -181,20 +205,33 @@ class ModelMeta implements ArrayAccess {
     function setupLazy($what) {
         switch ($what) {
         case 'fields':
-            $this->base['fields'] = self::inspectFields();
+            $this->meta['fields'] = self::inspectFields();
             break;
-        case 'newInstance':
-            $class_repr = sprintf(
+        default:
+            throw new Exception($what . ': No such meta-data');
+        }
+    }
+
+    /**
+     * Create a new instance of the model, optionally hydrating it with the
+     * given hash table. The constructor is not called, which leaves the
+     * default constructor free to assume new object status.
+     */
+    function newInstance($props=false) {
+        if (!isset($this->new)) {
+            $this->new = sprintf(
                 'O:%d:"%s":0:{}',
                 strlen($this->model), $this->model
             );
-            $this->base['newInstance'] = function() use ($class_repr) {
-                return unserialize($class_repr);
-            };
-            break;
-        default:
-            throw new Exception($what . ': No such meta-data');
         }
+        // TODO: Compare timing between unserialize() and
+        //       ReflectionClass::newInstanceWithoutConstructor
+        $instance = unserialize($this->new);
+        // Hydrate if props were included
+        if (is_array($props)) {
+            $instance->ht = $props;
+        }
+        return $instance;
     }
 
     function inspectFields() {
@@ -232,8 +269,12 @@ class VerySimpleModel {
     var $__deleted__ = false;
     var $__deferred__ = array();
 
-    function __construct($row) {
-        $this->ht = $row;
+    function __construct($row=false) {
+        if (is_array($row))
+            foreach ($row as $field=>$value)
+                if (!is_array($value))
+                    $this->set($field, $value);
+        $this->__new__ = true;
     }
 
     function get($field, $default=false) {
@@ -354,7 +395,19 @@ class VerySimpleModel {
                 }
                 // Pass. Set local field to NULL in logic below
             }
-            elseif ($value instanceof $j['fkey'][0]) {
+            elseif ($value instanceof VerySimpleModel) {
+                // Ensure that the model being assigned as a relationship is
+                // an instance of the foreign model given in the
+                // relationship, or is a super class thereof. The super
+                // class case is used primary for the xxxThread classes
+                // which all extend from the base Thread class.
+                if (!$value instanceof $j['fkey'][0]
+                    && !$value::getMeta()->isSuperClassOf($j['fkey'][0])
+                ) {
+                    throw new InvalidArgumentException(
+                        sprintf(__('Expecting NULL or instance of %s. Got a %s instead'),
+                        $j['fkey'][0], is_object($value) ? get_class($value) : gettype($value)));
+                }
                 // Capture the object under the object's field name
                 $this->ht[$field] = $value;
                 if ($value->__new__)
@@ -364,11 +417,6 @@ class VerySimpleModel {
                     $value = $value->get($j['fkey'][1]);
                 // Fall through to the standard logic below
             }
-            else
-                throw new InvalidArgumentException(
-                    sprintf(__('Expecting NULL or instance of %s. Got a %s instead'),
-                    $j['fkey'][0], is_object($value) ? get_class($value) : gettype($value)));
-
             // Capture the foreign key id value
             $field = $j['local'];
         }
@@ -402,18 +450,13 @@ class VerySimpleModel {
     }
 
     function __onload() {}
-    static function __oninspect() {}
-
-    static function _inspect() {
-        static::$meta = new ModelMeta(get_called_class());
-
-        // Let the model participate
-        static::__oninspect();
-    }
 
     static function getMeta($key=false) {
-        if (!static::$meta instanceof ModelMeta)
-            static::_inspect();
+        if (!static::$meta instanceof ModelMeta
+            || get_called_class() != static::$meta->model
+        ) {
+            static::$meta = new ModelMeta(get_called_class());
+        }
         $M = static::$meta;
         return ($key) ? $M->offsetGet($key) : $M;
     }
@@ -587,17 +630,6 @@ class VerySimpleModel {
         return true;
     }
 
-    static function create($ht=false) {
-        if (!$ht) $ht=array();
-        $class = get_called_class();
-        $i = new $class(array());
-        $i->__new__ = true;
-        foreach ($ht as $field=>$value)
-            if (!is_array($value))
-                $i->set($field, $value);
-        return $i;
-    }
-
     private function getPk() {
         $pk = array();
         foreach ($this::getMeta('pk') as $f)
@@ -1480,19 +1512,13 @@ class ModelInstanceManager extends ResultSet {
         // Check the cache for the model instance first
         if (!($m = self::checkCache($modelClass, $fields))) {
             // Construct and cache the object
-            $m = new $modelClass($fields);
+            $m = $modelClass::$meta->newInstance($fields);
             // XXX: defer may refer to fields not in this model
             $m->__deferred__ = $this->queryset->defer;
             $m->__onload();
             if ($cache)
                 $this->cache($m);
         }
-        elseif (get_class($m) != $modelClass) {
-            // Change the class of the object to be returned to match what
-            // was expected
-            // TODO: Emit a warning?
-            $m = new $modelClass($m->ht);
-        }
         // Wrap annotations in an AnnotatedModel
         if ($extras) {
             $m = new AnnotatedModel($m, $extras);
diff --git a/include/class.page.php b/include/class.page.php
index fa0eb8f77..920c2ee88 100644
--- a/include/class.page.php
+++ b/include/class.page.php
@@ -168,7 +168,7 @@ class Page extends VerySimpleModel {
     /* ------------------> Static methods <--------------------- */
 
     static function create($vars=false) {
-        $page = parent::create($vars);
+        $page = new static($vars);
         $page->created = SqlFunction::NOW();
         return $page;
     }
diff --git a/include/class.role.php b/include/class.role.php
index 005bc0ff5..f584cfa43 100644
--- a/include/class.role.php
+++ b/include/class.role.php
@@ -193,7 +193,7 @@ class Role extends RoleModel {
     }
 
     static function create($vars=false) {
-        $role = parent::create($vars);
+        $role = new static($vars);
         $role->created = SqlFunction::NOW();
         return $role;
     }
diff --git a/include/class.search.php b/include/class.search.php
index 69d21401c..25617b27b 100644
--- a/include/class.search.php
+++ b/include/class.search.php
@@ -983,7 +983,7 @@ class SavedSearch extends VerySimpleModel {
     }
 
     static function create($vars=array()) {
-        $inst = parent::create($vars);
+        $inst = new static($vars);
         $inst->created = SqlFunction::NOW();
         return $inst;
     }
diff --git a/include/class.sequence.php b/include/class.sequence.php
index cf75701fb..912e7cd28 100644
--- a/include/class.sequence.php
+++ b/include/class.sequence.php
@@ -205,7 +205,7 @@ class Sequence extends VerySimpleModel {
     }
 
     function __create($data) {
-        $instance = parent::create($data);
+        $instance = new static($data);
         $instance->save();
         return $instance;
     }
@@ -214,9 +214,6 @@ class Sequence extends VerySimpleModel {
 class RandomSequence extends Sequence {
     var $padding = '0';
 
-    // Override the ORM constructor and do nothing
-    function __construct($ht=false) {}
-
     function __next($digits=6) {
         if ($digits < 6)
             $digits = 6;
diff --git a/include/class.sla.php b/include/class.sla.php
index 936f300a4..d961ebcba 100644
--- a/include/class.sla.php
+++ b/include/class.sla.php
@@ -203,7 +203,7 @@ implements TemplateVariable {
     }
 
     static function create($vars=false, &$errors=array()) {
-        $sla = parent::create($vars);
+        $sla = new static($vars);
         $sla->created = SqlFunction::NOW();
         return $sla;
     }
diff --git a/include/class.staff.php b/include/class.staff.php
index b511f633a..a5e56c22a 100644
--- a/include/class.staff.php
+++ b/include/class.staff.php
@@ -702,7 +702,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable {
         while(list(, list($team_id, $alerts)) = each($membership)) {
             $member = $this->teams->findFirst(array('team_id' => $team_id));
             if (!$member) {
-                $this->teams->add($member = TeamMember::create(array(
+                $this->teams->add($member = new TeamMember(array(
                     'team_id' => $team_id,
                 )));
             }
@@ -819,7 +819,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable {
 
 
     static function create($vars=false) {
-        $staff = parent::create($vars);
+        $staff = new static($vars);
         $staff->created = SqlFunction::NOW();
         return $staff;
     }
@@ -1093,7 +1093,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable {
                 $errors['dept_access'][$dept_id] = __('Agent already has access to this department');
             $da = $this->dept_access->findFirst(array('dept_id' => $dept_id));
             if (!isset($da)) {
-                $da = StaffDeptAccess::create(array(
+                $da = new StaffDeptAccess(array(
                     'dept_id' => $dept_id, 'role_id' => $role_id
                 ));
                 $this->dept_access->add($da);
diff --git a/include/class.task.php b/include/class.task.php
index 1aecea023..5350d9b60 100644
--- a/include/class.task.php
+++ b/include/class.task.php
@@ -1269,7 +1269,7 @@ class Task extends TaskModel implements RestrictedAccess, Threadable {
                 || !$thisstaff->hasPerm(Task::PERM_CREATE, false))
             return null;
 
-        $task = parent::create(array(
+        $task = new static(array(
             'flags' => self::ISOPEN,
             'object_id' => $vars['object_id'],
             'object_type' => $vars['object_type'],
diff --git a/include/class.team.php b/include/class.team.php
index 1e772d16d..d6963eb92 100644
--- a/include/class.team.php
+++ b/include/class.team.php
@@ -203,7 +203,7 @@ implements TemplateVariable {
               $errors['members'][$staff_id] = __('No such agent');
           $member = $this->members->findFirst(array('staff_id' => $staff_id));
           if (!isset($member)) {
-              $member = TeamMember::create(array('staff_id' => $staff_id));
+              $member = new TeamMember(array('staff_id' => $staff_id));
               $this->members->add($member);
           }
           $member->setAlerts($alerts);
@@ -305,7 +305,7 @@ implements TemplateVariable {
     }
 
     static function create($vars=false) {
-        $team = parent::create($vars);
+        $team = new static($vars);
         $team->created = SqlFunction::NOW();
         return $team;
     }
diff --git a/include/class.thread.php b/include/class.thread.php
index 0bb394aab..f0a92c78b 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -534,10 +534,7 @@ class Thread extends VerySimpleModel {
     }
 
     static function create($vars=false) {
-        // $vars is expected to be an array
-        assert(is_array($vars));
-
-        $inst = parent::create($vars);
+        $inst = new static($vars);
         $inst->created = SqlFunction::NOW();
         return $inst;
     }
@@ -964,14 +961,14 @@ implements TemplateVariable {
             $fileId = $file;
         elseif ($file instanceof AttachmentFile)
             $fileId = $file->getId();
-        elseif ($F = AttachmentFile::createFile($file))
+        elseif ($F = AttachmentFile::create($file))
             $fileId = $F->getId();
         elseif (is_array($file) && isset($file['id']))
             $fileId = $file['id'];
         else
             return false;
 
-        $att = Attachment::create(array(
+        $att = new Attachment(array(
             'type' => 'H',
             'object_id' => $this->getId(),
             'file_id' => $fileId,
@@ -1067,7 +1064,7 @@ implements TemplateVariable {
         if (!$id || !$mid)
             return false;
 
-        $this->email_info = ThreadEntryEmailInfo::create(array(
+        $this->email_info = new ThreadEntryEmailInfo(array(
             'thread_entry_id' => $id,
             'mid' => $mid,
         ));
@@ -1359,7 +1356,7 @@ implements TemplateVariable {
         if ($poster && is_object($poster))
             $poster = (string) $poster;
 
-        $entry = parent::create(array(
+        $entry = new static(array(
             'created' => SqlFunction::NOW(),
             'type' => $vars['type'],
             'thread_id' => $vars['threadId'],
@@ -1703,7 +1700,7 @@ class ThreadEvent extends VerySimpleModel {
     }
 
     static function create($ht=false, $user=false) {
-        $inst = parent::create($ht);
+        $inst = new static($ht);
         $inst->timestamp = SqlFunction::NOW();
 
         global $thisstaff, $thisclient;
@@ -1721,7 +1718,7 @@ class ThreadEvent extends VerySimpleModel {
     }
 
     static function forTicket($ticket, $state, $user=false) {
-        $inst = static::create(array(
+        $inst = self::create(array(
             'staff_id' => $ticket->getStaffId(),
             'team_id' => $ticket->getTeamId(),
             'dept_id' => $ticket->getDeptId(),
@@ -2586,7 +2583,6 @@ implements TemplateVariable {
 
 // Ticket thread class
 class TicketThread extends ObjectThread {
-
     static function create($ticket=false) {
         assert($ticket !== false);
 
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 6008a6eaa..9d32abcde 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -3001,7 +3001,7 @@ implements RestrictedAccess, Threadable {
      *
      *  $autorespond and $alertstaff overrides config settings...
      */
-    static function create2($vars, &$errors, $origin, $autorespond=true,
+    static function create($vars, &$errors, $origin, $autorespond=true,
             $alertstaff=true) {
         global $ost, $cfg, $thisclient, $thisstaff;
 
@@ -3294,7 +3294,7 @@ implements RestrictedAccess, Threadable {
 
         //We are ready son...hold on to the rails.
         $number = $topic ? $topic->getNewTicketNumber() : $cfg->getNewTicketNumber();
-        $ticket = parent::create(array(
+        $ticket = new static(array(
             'created' => SqlFunction::NOW(),
             'lastupdate' => SqlFunction::NOW(),
             'number' => $number,
@@ -3504,7 +3504,7 @@ implements RestrictedAccess, Threadable {
         $create_vars['cannedattachments']
             = $tform->getField('message')->getWidget()->getAttachments()->getClean();
 
-        if (!($ticket=Ticket::create2($create_vars, $errors, 'staff', false)))
+        if (!($ticket=self::create($create_vars, $errors, 'staff', false)))
             return false;
 
         $vars['msgId']=$ticket->getLastMsgId();
diff --git a/include/class.topic.php b/include/class.topic.php
index 731ea0e1c..63e72558d 100644
--- a/include/class.topic.php
+++ b/include/class.topic.php
@@ -278,7 +278,7 @@ implements TemplateVariable {
     /*** Static functions ***/
 
     static function create($vars=array()) {
-        $topic = parent::create($vars);
+        $topic = new static($vars);
         $topic->created = SqlFunction::NOW();
         return $topic;
     }
@@ -506,14 +506,15 @@ implements TemplateVariable {
                     // Don't add a form more than once
                     continue;
                 }
-                TopicFormModel::create(array(
+                $tf = new TopicFormModel(array(
                     'topic_id' => $this->getId(),
                     'form_id' => $id,
                     'sort' => $sort + 1,
                     'extra' => JsonDataEncoder::encode(
                         array('disable' => $find_disabled($form))
                     )
-                ))->save();
+                ));
+                $tf->save();
             }
         }
         return true;
diff --git a/include/class.translation.php b/include/class.translation.php
index 77786de45..711fe1d0c 100644
--- a/include/class.translation.php
+++ b/include/class.translation.php
@@ -1018,7 +1018,7 @@ class CustomDataTranslation extends VerySimpleModel {
             $ht['text'] = static::encodeComplex($ht['text']);
             $ht['flags'] = ($ht['flags'] ?: 0) | self::FLAG_COMPLEX;
         }
-        return parent::create($ht);
+        return new static($ht);
     }
 
     static function allTranslations($msgid, $type='phrase', $lang=false) {
diff --git a/include/class.upgrader.php b/include/class.upgrader.php
index 37df892c3..87fb63c89 100644
--- a/include/class.upgrader.php
+++ b/include/class.upgrader.php
@@ -87,7 +87,7 @@ class Upgrader {
 
         //Create a ticket to make the system warm and happy.
         $errors = array();
-        Ticket::create2($vars, $errors, 'api', false, false);
+        Ticket::create($vars, $errors, 'api', false, false);
     }
 
     function getMode() {
diff --git a/include/class.user.php b/include/class.user.php
index 4d106e27c..82e1f9cfe 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -206,7 +206,7 @@ implements TemplateVariable {
             elseif (!$name)
                 list($name) = explode('@', $vars['email'], 2);
 
-            $user = User::create(array(
+            $user = new User(array(
                 'name' => Format::htmldecode(Format::sanitize($name, false)),
                 'created' => new SqlFunction('NOW'),
                 'updated' => new SqlFunction('NOW'),
@@ -871,7 +871,7 @@ class UserEmail extends UserEmailModel {
     static function ensure($address) {
         $email = static::lookup(array('address'=>$address));
         if (!$email) {
-            $email = static::create(array('address'=>$address));
+            $email = new static(array('address'=>$address));
             $email->save();
         }
         return $email;
@@ -1146,7 +1146,7 @@ class UserAccount extends VerySimpleModel {
     }
 
     static function createForUser($user, $defaults=false) {
-        $acct = static::create(array('user_id'=>$user->getId()));
+        $acct = new static(array('user_id'=>$user->getId()));
         if ($defaults && is_array($defaults)) {
             foreach ($defaults as $k => $v)
                 $acct->set($k, $v);
@@ -1181,12 +1181,11 @@ class UserAccount extends VerySimpleModel {
 
         if ($errors) return false;
 
-        $account = UserAccount::create(array('user_id' => $user->getId()));
-        if (!$account)
-            return false;
-
-        $account->set('timezone', $vars['timezone']);
-        $account->set('backend', $vars['backend']);
+        $account = new UserAccount(array(
+            'user_id' => $user->getId(),
+            'timezone' => $vars['timezone'],
+            'backend' => $vars['backend'],
+        ));
 
         if ($vars['username'] && strcasecmp($vars['username'], $user->getEmail()))
             $account->set('username', $vars['username']);
diff --git a/include/upgrader/streams/core/15b30765-dd0022fb.task.php b/include/upgrader/streams/core/15b30765-dd0022fb.task.php
index 970136b1b..beafd9d7d 100644
--- a/include/upgrader/streams/core/15b30765-dd0022fb.task.php
+++ b/include/upgrader/streams/core/15b30765-dd0022fb.task.php
@@ -262,7 +262,7 @@ class OldOneSixFile extends VerySimpleModel {
     );
 
     static function create($info) {
-        $I = parent::create($info);
+        $I = new static($info);
         $I->save();
         return $I;
     }
diff --git a/include/upgrader/streams/core/934954de-f1ccd3bb.task.php b/include/upgrader/streams/core/934954de-f1ccd3bb.task.php
index 74a4006fd..041bfad9a 100644
--- a/include/upgrader/streams/core/934954de-f1ccd3bb.task.php
+++ b/include/upgrader/streams/core/934954de-f1ccd3bb.task.php
@@ -7,7 +7,7 @@ class FileImport extends MigrationTask {
         $i18n = new Internationalization('en_US');
         $files = $i18n->getTemplate('file.yaml')->getData();
         foreach ($files as $f) {
-            if (!($file = AttachmentFile::createFile($f)))
+            if (!($file = AttachmentFile::create($f)))
                 continue;
 
             // Ensure the new files are never deleted (attached to Disk)
diff --git a/open.php b/open.php
index 8359edbf3..a081c29c9 100644
--- a/open.php
+++ b/open.php
@@ -39,7 +39,7 @@ if ($_POST) {
     // submitted will be displayed back to the user
     Draft::deleteForNamespace('ticket.client.'.substr(session_id(), -12));
     //Ticket::create...checks for errors..
-    if(($ticket=Ticket::create2($vars, $errors, SOURCE))){
+    if(($ticket=Ticket::create($vars, $errors, SOURCE))){
         $msg=__('Support ticket request created');
         // Drop session-backed form data
         unset($_SESSION[':form-data']);
diff --git a/setup/inc/class.installer.php b/setup/inc/class.installer.php
index 4142f7785..85441fedd 100644
--- a/setup/inc/class.installer.php
+++ b/setup/inc/class.installer.php
@@ -288,7 +288,7 @@ class Installer extends SetupWizard {
         $errors = array();
         $ticket_vars = $i18n->getTemplate('templates/ticket/installed.yaml')
             ->getData();
-        $ticket = Ticket::create2($ticket_vars, $errors, 'api', false, false);
+        $ticket = Ticket::create($ticket_vars, $errors, 'api', false, false);
 
         if ($ticket
             && ($org = Organization::objects()->order_by('id')->one())
-- 
GitLab