PluginProbe
MailPoet – Newsletters, Email Marketing, and Automation / 3.89.4
MailPoet – Newsletters, Email Marketing, and Automation v3.89.4
5.38.0 5.37.0 5.36.1 5.36.0 5.35.1 5.35.0 5.34.3 5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 All 542 releases
mailpoet / lib / Models / Model.php

Model.php in MailPoet – Newsletters, Email Marketing, and Automation 3.89.4, at lib/Models/Model.php

405 lines 13.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace MailPoet\Models;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\Util\Helpers;
9 use MailPoet\WP\Functions as WPFunctions;
10
11 /**
12 * @method static array|string getConfig($key = null, $connection_name = self::DEFAULT_CONNECTION)
13 * @method static null resetConfig()
14 * @method static self forTable($table_name, $connection_name = self::DEFAULT_CONNECTION)
15 * @method static null setDb($db, $connection_name = self::DEFAULT_CONNECTION)
16 * @method static null resetDb()
17 * @method static null setupLimitClauseStyle($connection_name)
18 * @method static \PDO getDb($connection_name = self::DEFAULT_CONNECTION)
19 * @method static bool rawExecute($query, $parameters = array())
20 * @method static \PDOStatement getLastStatement()
21 * @method static string getLastQuery($connection_name = null)
22 * @method static array getQueryLog($connection_name = self::DEFAULT_CONNECTION)
23 * @method array getConnectionNames()
24 * @method $this useIdColumn($id_column)
25 * @method $this|false findOne($id=null)
26 * @method static static|false findOne($id=null)
27 * @method array findMany()
28 * @method static array findMany()
29 * @method \MailPoetVendor\Idiorm\IdiormResultSet findResultSet()
30 * @method array findArray()
31 * @method static array findArray()
32 * @method $this forceAllDirty()
33 * @method $this select_expr(string $expr, string $alias=null)
34 * @method $this rawQuery($query, $parameters = array())
35 * @method static $this rawQuery($query, $parameters = array())
36 * @method $this tableAlias($alias)
37 * @method static $this tableAlias($alias)
38 * @method int countNullIdColumns()
39 * @method $this select($column, $alias=null)
40 * @method static $this select($column, $alias=null)
41 * @method $this selectExpr($expr, $alias=null)
42 * @method static $this selectExpr($expr, $alias=null)
43 * @method $this selectMany(...$values)
44 * @method static static selectMany(...$values)
45 * @method static selectManyExpr($values)
46 * @method $this rawJoin(string $table, string|array $constraint, string $table_alias, array $parameters = array())
47 * @method $this innerJoin(string $table, string|array $constraint, string $table_alias=null)
48 * @method $this join(string $table, string|array $constraint, string $table_alias=null)
49 * @method static static join(string $table, string|array $constraint, string $table_alias=null)
50 * @method $this leftOuterJoin(string $table, string|array $constraint, string $table_alias=null)
51 * @method $this rightOuterJoin(string $table, string|array $constraint, string $table_alias=null)
52 * @method $this fullOuterJoin(string $table, string|array $constraint, string $table_alias=null)
53 * @method $this where($column_name, $value=null)
54 * @method static $this where($column_name, $value=null)
55 * @method $this whereEqual($column_name, $value=null)
56 * @method static $this whereEqual($column_name, $value=null)
57 * @method $this whereNotEqual($column_name, $value=null)
58 * @method static $this whereNotEqual($column_name, $value=null)
59 * @method $this whereIdIs($id)
60 * @method $this whereAnyIs($values, $operator='=')
61 * @method static $this whereAnyIs($values, $operator='=')
62 * @method $this whereIdIn($ids)
63 * @method static static whereIdIn($ids)
64 * @method $this whereLike($column_name, $value=null)
65 * @method static $this whereLike($column_name, $value=null)
66 * @method $this whereNotLike($column_name, $value=null)
67 * @method $this whereGt($column_name, $value=null)
68 * @method static $this whereGt($column_name, $value=null)
69 * @method static $this whereLt($column_name, $value=null)
70 * @method $this whereGte($column_name, $value=null)
71 * @method $this whereLte($column_name, $value=null)
72 * @method $this whereIn($column_name, $values)
73 * @method static $this whereIn($column_name, $values)
74 * @method $this whereNotIn($column_name, $values)
75 * @method static $this whereNotIn($column_name, $values)
76 * @method $this whereNull($column_name)
77 * @method static static whereNull($column_name)
78 * @method $this whereNotNull($column_name)
79 * @method static $this whereNotNull($column_name)
80 * @method $this whereRaw($clause, $parameters=array())
81 * @method static $this whereRaw($clause, $parameters=array())
82 * @method $this deleteMany()
83 * @method static $this deleteMany()
84 * @method $this orderByDesc($column_name)
85 * @method static $this orderByDesc($column_name)
86 * @method $this orderByAsc($column_name)
87 * @method static $this orderByAsc($column_name)
88 * @method $this orderByExpr($clause)
89 * @method $this groupBy($column_name)
90 * @method $this groupByExpr($expr)
91 * @method $this havingEqual($column_name, $value=null)
92 * @method $this havingNotEqual($column_name, $value=null)
93 * @method $this havingIdIs($id)
94 * @method $this havingLike($column_name, $value=null)
95 * @method $this havingNotLike($column_name, $value=null)
96 * @method $this havingGt($column_name, $value=null)
97 * @method $this havingLt($column_name, $value=null)
98 * @method $this havingGte($column_name, $value=null)
99 * @method $this havingLte($column_name, $value=null)
100 * @method $this havingIn($column_name, $values=null)
101 * @method $this havingNotIn($column_name, $values=null)
102 * @method $this havingNull($column_name)
103 * @method $this havingNotNull($column_name)
104 * @method $this havingRaw($clause, $parameters=array())
105 * @method static $this clearCache($table_name = null, $connection_name = self::DEFAULT_CONNECTION)
106 * @method bool setExpr($key, $value = null)
107 * @method bool isDirty($key)
108 * @method static static filter(...$args)
109 * @method array asArray(...$args)
110 * @method $this hasMany($associated_class_name, $foreign_key_name=null, $foreign_key_name_in_current_models_table=null, $connection_name=null)
111 * @method $this hasManyThrough($associated_class_name, $join_class_name=null, $key_to_base_table=null, $key_to_associated_table=null, $key_in_base_table=null, $key_in_associated_table=null, $connection_name=null)
112 * @method mixed hasOne($associated_class_name, $foreign_key_name=null, $foreign_key_name_in_current_models_table=null, $connection_name=null)
113 * @method $this|bool create($data=null)
114 * @method static $this|bool create($data=null)
115 * @method int count()
116 * @method static int count()
117 * @method int sum($column_name)
118 * @method int min($column_name)
119 * @method int max($column_name)
120 * @method int avg($column_name)
121 * @method static int sum($column_name)
122 * @method static int min($column_name)
123 * @method static int max($column_name)
124 * @method static int avg($column_name)
125 * @method static static limit(int $limit)
126 * @method static static distinct()
127 * @method $this set(string|array $key, string|null $value = null)
128 *
129 * @property string|null $createdAt
130 * @property string|null $updatedAt
131 * @property string|null $id
132 * @property string|null $first
133 * @property string|null $last
134 */
135
136 class Model extends \MailPoetVendor\Sudzy\ValidModel {
137 const DUPLICATE_RECORD = 23000;
138
139 public static $_table; // phpcs:ignore PSR2.Classes.PropertyDeclaration
140 protected $_errors; // phpcs:ignore PSR2.Classes.PropertyDeclaration
141 protected $newRecord;
142
143 public function __construct() {
144 $this->_errors = [];
145 $validator = new ModelValidator();
146 parent::__construct($validator);
147 }
148
149 /**
150 * @return static
151 */
152 public static function create() {
153 $created = parent::create();
154 if (is_bool($created)) {
155 throw new \Exception('ORM is not initialised');
156 }
157 return $created;
158 }
159
160 /**
161 * Creates a row, or updates it if already exists. It tries to find the existing
162 * row by `id` (if given in `$data`), or by the given `$keys`. If `$onCreate` is
163 * given, it's used to transform `$data` before creating the new row.
164 *
165 * @param array $data
166 * @param array|bool $keys
167 * @param callable|bool $onCreate
168 * @return self
169 */
170 static protected function _createOrUpdate($data = [], $keys = false, $onCreate = false) {
171 $model = false;
172
173 if (isset($data['id']) && (int)$data['id'] > 0) {
174 $model = static::findOne((int)$data['id']);
175 }
176
177 if ($model === false && !empty($keys) && is_array($keys)) {
178 foreach ($keys as $field => $value) {
179 if ($model === false) {
180 $model = static::where($field, $value);
181 } else {
182 $model = $model->where($field, $value);
183 }
184 }
185 if ($model !== false) $model = $model->findOne();
186 }
187
188 if ($model === false) {
189 if (!empty($onCreate) && is_callable($onCreate)) {
190 $data = $onCreate($data);
191 }
192 $model = static::create();
193 $model->hydrate($data);
194 } else {
195 unset($data['id']);
196 $model->set($data);
197 }
198
199 return $model->save();
200 }
201
202 static public function createOrUpdate($data = []) {
203 return self::_createOrUpdate($data);
204 }
205
206 public function getErrors() {
207 if (empty($this->_errors)) {
208 return false;
209 } else {
210 return $this->_errors;
211 }
212 }
213
214 public function setError($error = '', $errorCode = null) {
215 if (!$errorCode) {
216 $errorCode = count($this->_errors);
217 }
218 if (!empty($error)) {
219 if (is_array($error)) {
220 $this->_errors = array_merge($this->_errors, $error);
221 $this->_errors = array_unique($this->_errors);
222 } else {
223 $this->_errors[$errorCode] = $error;
224 }
225 }
226 }
227
228 /**
229 * @return static
230 * @phpstan-ignore-next-line Our Model has incompatible return type with parent
231 */
232 public function save() {
233 $this->setTimestamp();
234 $this->newRecord = $this->isNew();
235 try {
236 parent::save();
237 } catch (\MailPoetVendor\Sudzy\ValidationException $e) {
238 $this->setError($e->getValidationErrors());
239 } catch (\PDOException $e) {
240 switch ($e->getCode()) {
241 case 23000:
242 preg_match("/for key '(?:.*\.)*(.*?)'/i", $e->getMessage(), $matches);
243 if (isset($matches[1])) {
244 $column = $matches[1];
245 $this->setError(
246 sprintf(
247 WPFunctions::get()->__('Another record already exists. Please specify a different "%1$s".', 'mailpoet'),
248 $column
249 ),
250 Model::DUPLICATE_RECORD
251 );
252 } else {
253 $this->setError($e->getMessage());
254 }
255 break;
256 default:
257 $this->setError($e->getMessage());
258 }
259 }
260 return $this;
261 }
262
263 public function isNew() {
264 return (isset($this->newRecord)) ?
265 $this->newRecord :
266 parent::isNew();
267 }
268
269 public function trash() {
270 return $this->set_expr('deleted_at', 'NOW()')->save();
271 }
272
273 public static function bulkTrash($orm) {
274 $model = get_called_class();
275 $count = self::bulkAction($orm, function($ids) use ($model) {
276 $model::rawExecute(join(' ', [
277 'UPDATE `' . $model::$_table . '`',
278 'SET `deleted_at` = NOW()',
279 'WHERE `id` IN (' . rtrim(str_repeat('?,', count($ids)), ',') . ')',
280 ]), $ids);
281 });
282
283 return ['count' => $count];
284 }
285
286 public static function bulkDelete($orm) {
287 $model = get_called_class();
288 $count = self::bulkAction($orm, function($ids) use ($model) {
289 $model::whereIn('id', $ids)->deleteMany();
290 });
291
292 return ['count' => $count];
293 }
294
295 public function restore() {
296 return $this->set_expr('deleted_at', 'NULL')->save();
297 }
298
299 public static function bulkRestore($orm) {
300 $model = get_called_class();
301 $count = self::bulkAction($orm, function($ids) use ($model) {
302 $model::rawExecute(join(' ', [
303 'UPDATE `' . $model::$_table . '`',
304 'SET `deleted_at` = NULL',
305 'WHERE `id` IN (' . rtrim(str_repeat('?,', count($ids)), ',') . ')',
306 ]), $ids);
307 });
308
309 return ['count' => $count];
310 }
311
312 public static function bulkAction($orm, $callback = false) {
313 $total = $orm->count();
314
315 if ($total === 0) return false;
316
317 $rows = $orm->select(static::$_table . '.id')
318 ->offset(null)
319 ->limit(null)
320 ->findArray();
321
322 $ids = array_map(function($model) {
323 return (int)$model['id'];
324 }, $rows);
325
326 if (is_callable($callback)) {
327 $callback($ids);
328 }
329
330 // get number of affected rows
331 return $orm->get_last_statement()
332 ->rowCount();
333 }
334
335 public function duplicate($data = []) {
336 $model = get_called_class();
337 $modelData = array_merge($this->asArray(), $data);
338 unset($modelData['id']);
339
340 $duplicate = $model::create();
341 $duplicate->hydrate($modelData);
342 $duplicate->set_expr('created_at', 'NOW()');
343 $duplicate->set_expr('updated_at', 'NOW()');
344 if (isset($modelData['deleted_at'])) {
345 $duplicate->set_expr('deleted_at', 'NULL');
346 }
347
348 $duplicate->save();
349 return $duplicate;
350 }
351
352 public function setTimestamp() {
353 if ($this->createdAt === null) {
354 $this->set_expr('created_at', 'NOW()');
355 }
356 }
357
358 public static function getPublished() {
359 return static::whereNull('deleted_at');
360 }
361
362 public static function getTrashed() {
363 return static::whereNotNull('deleted_at');
364 }
365
366 /**
367 * Rethrow PDOExceptions to prevent exposing sensitive data in stack traces
368 */
369 public static function __callStatic($method, $parameters) {
370 try {
371 return parent::__callStatic($method, $parameters);
372 } catch (\PDOException $e) {
373 throw new \Exception($e->getMessage());
374 }
375 }
376
377 public function validate() {
378 $success = true;
379 foreach (array_keys($this->_validations) as $field) {
380 $success = $success && $this->validateField($field, $this->$field);
381 }
382 $this->setError($this->getValidationErrors());
383 return $success;
384 }
385
386 public function __get($name) {
387 $value = parent::__get($name);
388 if ($value !== null) {
389 return $value;
390 }
391 $name = Helpers::camelCaseToUnderscore($name);
392 return parent::__get($name);
393 }
394
395 public function __set($name, $value) {
396 $name = Helpers::camelCaseToUnderscore($name);
397 parent::__set($name, $value);
398 }
399
400 public function __isset($name) {
401 $name = Helpers::camelCaseToUnderscore($name);
402 return parent::__isset($name);
403 }
404 }
405