| @@ -1,0 +1,405 @@ | ||
| 1 | +<?php | |
| 2 | +/** | |
| 3 | + * @package VikWP - Libraries | |
| 4 | + * @subpackage adapter.mvc | |
| 5 | + * @author E4J s.r.l. | |
| 6 | + * @copyright Copyright (C) 2023 E4J s.r.l. All Rights Reserved. | |
| 7 | + * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL | |
| 8 | + * @link https://vikwp.com | |
| 9 | + */ | |
| 10 | + | |
| 11 | +// No direct access | |
| 12 | +defined('ABSPATH') or die('No script kiddies please!'); | |
| 13 | + | |
| 14 | +JLoader::import('adapter.application.object'); | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * The model class used by the MVC framework. | |
| 18 | + * A model can be used by a controller or a view to handle | |
| 19 | + * a certain entity of the plugin. | |
| 20 | + * | |
| 21 | + * The model can be invoked when the value contained | |
| 22 | + * in $_REQUEST['task'] is equals to 'ComponentModel' + $_REQUEST['task']. | |
| 23 | + * | |
| 24 | + * e.g. $_REQUEST['task'] = 'groups.save' -> ComponentModelGroups | |
| 25 | + * | |
| 26 | + * @since 10.0 | |
| 27 | + */ | |
| 28 | +abstract class JModel extends JObject | |
| 29 | +{ | |
| 30 | + /** | |
| 31 | + * A list of model instances. | |
| 32 | + * | |
| 33 | + * @var array | |
| 34 | + */ | |
| 35 | + protected static $instances = array(); | |
| 36 | + | |
| 37 | + /** | |
| 38 | + * A list of included paths. | |
| 39 | + * | |
| 40 | + * @var array | |
| 41 | + * @since 10.1.24 | |
| 42 | + */ | |
| 43 | + protected static $paths = array(); | |
| 44 | + | |
| 45 | + /** | |
| 46 | + * The model name. | |
| 47 | + * e.g. ComponentModel[NAME] | |
| 48 | + * | |
| 49 | + * @var string | |
| 50 | + */ | |
| 51 | + protected $_name = null; | |
| 52 | + | |
| 53 | + /** | |
| 54 | + * The component name. | |
| 55 | + * e.g. [COMPONENT]ModelName | |
| 56 | + * | |
| 57 | + * @var string | |
| 58 | + */ | |
| 59 | + protected $_component = null; | |
| 60 | + | |
| 61 | + /** | |
| 62 | + * The application client. | |
| 63 | + * | |
| 64 | + * @var string | |
| 65 | + */ | |
| 66 | + protected $_client = null; | |
| 67 | + | |
| 68 | + /** | |
| 69 | + * The database table name. | |
| 70 | + * | |
| 71 | + * @var array | |
| 72 | + * @since 10.1.35 | |
| 73 | + */ | |
| 74 | + protected $_config; | |
| 75 | + | |
| 76 | + /** | |
| 77 | + * Tries to load the specified model, only creating it if | |
| 78 | + * it doesn't exist yet. | |
| 79 | + * | |
| 80 | + * @param string $type The model type to instantiate | |
| 81 | + * @param string $prefix Prefix for the model class name. | |
| 82 | + * @param array $config Configuration array for model. | |
| 83 | + * | |
| 84 | + * @return mixed The model instance if found, otherwise null. | |
| 85 | + * | |
| 86 | + * @since 10.1.35 Switch the ordering of $type and $prefix argument. | |
| 87 | + * @since 10.1.35 Added $config argument. | |
| 88 | + * @since 10.1.35 Removed $client, $table and $pk arguments. | |
| 89 | + */ | |
| 90 | + public static function getInstance($type, $prefix = '', $config = array()) | |
| 91 | + { | |
| 92 | + /** | |
| 93 | + * For backward compatibility, we should check whether the 3rd argument is passed, | |
| 94 | + * meaning that we need to force a client section in which to load the model. | |
| 95 | + * | |
| 96 | + * @since 10.1.35 | |
| 97 | + */ | |
| 98 | + if (is_string($config)) | |
| 99 | + { | |
| 100 | + $config = ['client' => $config]; | |
| 101 | + } | |
| 102 | + else | |
| 103 | + { | |
| 104 | + $config = (array) $config; | |
| 105 | + } | |
| 106 | + | |
| 107 | + if (empty($config['client'])) | |
| 108 | + { | |
| 109 | + $config['client'] = JFactory::getApplication()->isAdmin() ? 'admin' : 'site'; | |
| 110 | + } | |
| 111 | + | |
| 112 | + /** | |
| 113 | + * The current version of JModel is not using the same arguments as | |
| 114 | + * specified by Joomla, since the model name is always passed as | |
| 115 | + * first argument. | |
| 116 | + * | |
| 117 | + * For this reason, we need to swap the prefix and the name when | |
| 118 | + * passed using Joomla standards. This can be done when at least one | |
| 119 | + * of the following conditions is verified: | |
| 120 | + * | |
| 121 | + * - the name contains 'model' word | |
| 122 | + * - the name is actually the folder name and the prefix isn't | |
| 123 | + * | |
| 124 | + * @since 10.1.24 | |
| 125 | + */ | |
| 126 | + if (preg_match("/model/i", $type) | |
| 127 | + || (is_dir(WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $type) && !is_dir(WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . strtolower($prefix)))) | |
| 128 | + { | |
| 129 | + // swap name with prefix | |
| 130 | + $tmp = $type; | |
| 131 | + $type = $prefix; | |
| 132 | + $prefix = $tmp; | |
| 133 | + } | |
| 134 | + | |
| 135 | + // remove 'model' from prefix (if any) and make it lowercase | |
| 136 | + $prefix = strtolower(preg_replace("/model/i", '', $prefix)); | |
| 137 | + | |
| 138 | + $sign = serialize(array($prefix, $type, $config['client'])); | |
| 139 | + | |
| 140 | + if (!isset(static::$instances[$sign])) | |
| 141 | + { | |
| 142 | + static::$instances[$sign] = false; | |
| 143 | + | |
| 144 | + // create classname | |
| 145 | + $classname = ucfirst($prefix) . 'Model' . ucfirst($type); | |
| 146 | + | |
| 147 | + /** | |
| 148 | + * Merge default include path (the fallback) with specified directories. | |
| 149 | + * Then iterate the list to find the first available model. | |
| 150 | + * | |
| 151 | + * @since 10.1.24 | |
| 152 | + */ | |
| 153 | + $paths = array_merge( | |
| 154 | + // default include path | |
| 155 | + array( | |
| 156 | + /** | |
| 157 | + * Search also inside the libraries folder of the current plugin. | |
| 158 | + * Prioritize this folder to avoid conflicts with deprecated files. | |
| 159 | + * | |
| 160 | + * @since 10.1.35 | |
| 161 | + */ | |
| 162 | + implode(DIRECTORY_SEPARATOR, array(WP_PLUGIN_DIR, $prefix, 'libraries', 'mvc', $config['client'], 'models')), | |
| 163 | + implode(DIRECTORY_SEPARATOR, array(WP_PLUGIN_DIR, $prefix, $config['client'], 'models')), | |
| 164 | + ), | |
| 165 | + // specified include paths | |
| 166 | + self::addIncludePath() | |
| 167 | + ); | |
| 168 | + | |
| 169 | + /** | |
| 170 | + * Iterate paths until we find an existing file (or till the list is empty). | |
| 171 | + * | |
| 172 | + * @since 10.1.54 Keep iterating until we find a compatible class instead of | |
| 173 | + * a compatible file, since different plugins might support the | |
| 174 | + * same file name. | |
| 175 | + */ | |
| 176 | + for ($i = 0, $path = null; $i < count($paths) && !static::$instances[$sign]; $i++) | |
| 177 | + { | |
| 178 | + // create path | |
| 179 | + $path = $paths[$i] . DIRECTORY_SEPARATOR . strtolower($type) . '.php'; | |
| 180 | + | |
| 181 | + // make sure the file exists | |
| 182 | + if (is_file($path)) | |
| 183 | + { | |
| 184 | + // include model | |
| 185 | + require_once $path; | |
| 186 | + | |
| 187 | + // make sure the class exists | |
| 188 | + if (class_exists($classname)) | |
| 189 | + { | |
| 190 | + // cache model instance | |
| 191 | + static::$instances[$sign] = new $classname($config); | |
| 192 | + } | |
| 193 | + } | |
| 194 | + } | |
| 195 | + } | |
| 196 | + | |
| 197 | + return static::$instances[$sign]; | |
| 198 | + } | |
| 199 | + | |
| 200 | + /** | |
| 201 | + * Add a directory where the class should search for models. | |
| 202 | + * You may either pass a string or an array of directories. | |
| 203 | + * | |
| 204 | + * @param mixed $path A path or array of paths to search. | |
| 205 | + * | |
| 206 | + * @return array An array with directory elements. | |
| 207 | + * | |
| 208 | + * @since 10.1.24 | |
| 209 | + */ | |
| 210 | + public static function addIncludePath($path = null) | |
| 211 | + { | |
| 212 | + if (!empty($path)) | |
| 213 | + { | |
| 214 | + JLoader::import('adapter.filesystem.path'); | |
| 215 | + | |
| 216 | + foreach ((array) $path as $includePath) | |
| 217 | + { | |
| 218 | + $includePath = JPath::clean($includePath); | |
| 219 | + | |
| 220 | + // check if the path is a dir | |
| 221 | + if (!is_dir($includePath)) | |
| 222 | + { | |
| 223 | + // extract name between component and models | |
| 224 | + if (preg_match("/components[\/\\\\]com_([a-z0-9_]+)[\/\\\\]models/", $includePath, $match)) | |
| 225 | + { | |
| 226 | + // access adapters of current plugin | |
| 227 | + $option = JFactory::getApplication()->input->get('option'); | |
| 228 | + // strip initial com_ from option name | |
| 229 | + $option = preg_replace("/^com_/", '', $option); | |
| 230 | + | |
| 231 | + // rewrite include path | |
| 232 | + $includePath = JPath::clean(WP_PLUGIN_DIR . '/' . $option . '/libraries/adapter/mvc/models/' . end($match)); | |
| 233 | + } | |
| 234 | + } | |
| 235 | + | |
| 236 | + // make sure the folder is not already in the list | |
| 237 | + if (!in_array($includePath, static::$paths)) | |
| 238 | + { | |
| 239 | + // push directory as first | |
| 240 | + array_unshift(static::$paths, $includePath); | |
| 241 | + } | |
| 242 | + } | |
| 243 | + } | |
| 244 | + | |
| 245 | + return static::$paths; | |
| 246 | + } | |
| 247 | + | |
| 248 | + /** | |
| 249 | + * Class constructor. | |
| 250 | + * | |
| 251 | + * @param array $config An array of configuration options. | |
| 252 | + * | |
| 253 | + * @since 10.1.35 Added $config argument. | |
| 254 | + * @since 10.1.35 Removed $table and $pk arguments. | |
| 255 | + */ | |
| 256 | + public function __construct($config = array()) | |
| 257 | + { | |
| 258 | + $option = $this->getComponentName(); | |
| 259 | + | |
| 260 | + if (!array_key_exists('table_path', $config) && $option) | |
| 261 | + { | |
| 262 | + /** | |
| 263 | + * In case the client hasn't been provided, assume we should load the models | |
| 264 | + * from the same path of the current application client (admin or site). | |
| 265 | + * | |
| 266 | + * @since 10.1.55 | |
| 267 | + */ | |
| 268 | + if (empty($config['client'])) | |
| 269 | + { | |
| 270 | + $config['client'] = JFactory::getApplication()->isAdmin() ? 'admin' : 'site'; | |
| 271 | + } | |
| 272 | + | |
| 273 | + $config['table_path'] = [ | |
| 274 | + JPath::clean(WP_PLUGIN_DIR . '/' . $option . '/libraries/mvc/' . $config['client'] . '/tables'), | |
| 275 | + JPath::clean(WP_PLUGIN_DIR . '/' . $option . '/' . $config['client'] . '/tables'), | |
| 276 | + ]; | |
| 277 | + } | |
| 278 | + | |
| 279 | + // set the default view search path | |
| 280 | + if (!empty($config['table_path'])) | |
| 281 | + { | |
| 282 | + $this->addTablePath($config['table_path']); | |
| 283 | + } | |
| 284 | + | |
| 285 | + $this->_config = $config; | |
| 286 | + } | |
| 287 | + | |
| 288 | + /** | |
| 289 | + * Adds to the stack of model table paths in LIFO order. | |
| 290 | + * | |
| 291 | + * @param mixed $path The directory as a string or directories as an array to add. | |
| 292 | + * | |
| 293 | + * @return void | |
| 294 | + * | |
| 295 | + * @since 10.1.35 | |
| 296 | + */ | |
| 297 | + public function addTablePath($path) | |
| 298 | + { | |
| 299 | + JTable::addIncludePath($path); | |
| 300 | + } | |
| 301 | + | |
| 302 | + /** | |
| 303 | + * Method to get a table object. | |
| 304 | + * | |
| 305 | + * @param string $name The table name. | |
| 306 | + * @param string $prefix The class prefix. | |
| 307 | + * @param array $options Configuration array for table. | |
| 308 | + * | |
| 309 | + * @return JTable A table object. | |
| 310 | + * | |
| 311 | + * @since 10.1.35 | |
| 312 | + */ | |
| 313 | + public function getTable($name = '', $prefix = 'JTable', $options = array()) | |
| 314 | + { | |
| 315 | + if (!$name) | |
| 316 | + { | |
| 317 | + $name = $this->getName(); | |
| 318 | + } | |
| 319 | + | |
| 320 | + return JTable::getInstance($name, $prefix, $options); | |
| 321 | + } | |
| 322 | + | |
| 323 | + /** | |
| 324 | + * Returns the model name. | |
| 325 | + * | |
| 326 | + * @return string Model name. | |
| 327 | + */ | |
| 328 | + public function getModelName() | |
| 329 | + { | |
| 330 | + if (is_null($this->_name)) | |
| 331 | + { | |
| 332 | + $class = get_class($this); | |
| 333 | + | |
| 334 | + if (preg_match("/Model(.*?)$/", $class, $match)) | |
| 335 | + { | |
| 336 | + $this->_name = strtolower($match[1]); | |
| 337 | + } | |
| 338 | + else | |
| 339 | + { | |
| 340 | + $this->_name = $class; | |
| 341 | + } | |
| 342 | + } | |
| 343 | + | |
| 344 | + return $this->_name; | |
| 345 | + } | |
| 346 | + | |
| 347 | + /** | |
| 348 | + * Returns the model name. | |
| 349 | + * Proxy for getModelName() method. | |
| 350 | + * | |
| 351 | + * @return string Model name. | |
| 352 | + * | |
| 353 | + * @since 1.0.1.35 | |
| 354 | + */ | |
| 355 | + public function getName() | |
| 356 | + { | |
| 357 | + return $this->getModelName(); | |
| 358 | + } | |
| 359 | + | |
| 360 | + /** | |
| 361 | + * Returns the component name. | |
| 362 | + * | |
| 363 | + * @return string Component name. | |
| 364 | + */ | |
| 365 | + public function getComponentName() | |
| 366 | + { | |
| 367 | + if (is_null($this->_component)) | |
| 368 | + { | |
| 369 | + $class = get_class($this); | |
| 370 | + | |
| 371 | + if (preg_match("/(.*?)Model/", $class, $match)) | |
| 372 | + { | |
| 373 | + $this->_component = strtolower($match[1]); | |
| 374 | + } | |
| 375 | + else | |
| 376 | + { | |
| 377 | + $this->_component = $class; | |
| 378 | + } | |
| 379 | + } | |
| 380 | + | |
| 381 | + return $this->_component; | |
| 382 | + } | |
| 383 | + | |
| 384 | + /** | |
| 385 | + * Returns the application client folder (admin or site). | |
| 386 | + * | |
| 387 | + * @return string The client. | |
| 388 | + */ | |
| 389 | + public function getClientFolder() | |
| 390 | + { | |
| 391 | + if (is_null($this->_client)) | |
| 392 | + { | |
| 393 | + $this->_client = JFactory::getApplication()->isAdmin() ? 'admin' : 'site'; | |
| 394 | + } | |
| 395 | + | |
| 396 | + return $this->_client; | |
| 397 | + } | |
| 398 | +} | |
| 399 | + | |
| 400 | +/** | |
| 401 | + * Alias for JModel, which is still used by the components. | |
| 402 | + * | |
| 403 | + * @since 10.1.24 | |
| 404 | + */ | |
| 405 | +class_alias('JModel', 'JModelLegacy'); | |