1<?php 2/** 3 * @package FrameworkOnFramework 4 * @subpackage table 5 * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. 6 * @license GNU General Public License version 2 or later; see LICENSE.txt 7 */ 8// Protect from unauthorized access 9defined('FOF_INCLUDED') or die; 10 11/** 12 * Normally this shouldn't be required. Some PHP versions, however, seem to 13 * require this. Why? No idea whatsoever. If I remove it, FOF crashes on some 14 * hosts. Same PHP version on another host and no problem occurs. Any takers? 15 */ 16if (class_exists('FOFTable', false)) 17{ 18 return; 19} 20 21if (!interface_exists('JTableInterface', true)) 22{ 23 interface JTableInterface {} 24} 25 26/** 27 * FrameworkOnFramework Table class. The Table is one part controller, one part 28 * model and one part data adapter. It's supposed to handle operations for single 29 * records. 30 * 31 * @package FrameworkOnFramework 32 * @since 1.0 33 */ 34class FOFTable extends FOFUtilsObject implements JTableInterface 35{ 36 /** 37 * Cache array for instances 38 * 39 * @var array 40 */ 41 protected static $instances = array(); 42 43 /** 44 * Include paths for searching for FOFTable classes. 45 * 46 * @var array 47 */ 48 protected static $_includePaths = array(); 49 50 /** 51 * The configuration parameters array 52 * 53 * @var array 54 */ 55 protected $config = array(); 56 57 /** 58 * Name of the database table to model. 59 * 60 * @var string 61 */ 62 protected $_tbl = ''; 63 64 /** 65 * Name of the primary key field in the table. 66 * 67 * @var string 68 */ 69 protected $_tbl_key = ''; 70 71 /** 72 * FOFDatabaseDriver object. 73 * 74 * @var FOFDatabaseDriver 75 */ 76 protected $_db; 77 78 /** 79 * Should rows be tracked as ACL assets? 80 * 81 * @var boolean 82 */ 83 protected $_trackAssets = false; 84 85 /** 86 * Does the resource support joomla tags? 87 * 88 * @var boolean 89 */ 90 protected $_has_tags = false; 91 92 /** 93 * The rules associated with this record. 94 * 95 * @var JAccessRules A JAccessRules object. 96 */ 97 protected $_rules; 98 99 /** 100 * Indicator that the tables have been locked. 101 * 102 * @var boolean 103 */ 104 protected $_locked = false; 105 106 /** 107 * If this is set to true, it triggers automatically plugin events for 108 * table actions 109 * 110 * @var boolean 111 */ 112 protected $_trigger_events = false; 113 114 /** 115 * Table alias used in queries 116 * 117 * @var string 118 */ 119 protected $_tableAlias = false; 120 121 /** 122 * Array with alias for "special" columns such as ordering, hits etc etc 123 * 124 * @var array 125 */ 126 protected $_columnAlias = array(); 127 128 /** 129 * If set to true, it enabled automatic checks on fields based on columns properties 130 * 131 * @var boolean 132 */ 133 protected $_autoChecks = false; 134 135 /** 136 * Array with fields that should be skipped by automatic checks 137 * 138 * @var array 139 */ 140 protected $_skipChecks = array(); 141 142 /** 143 * Does the table actually exist? We need that to avoid PHP notices on 144 * table-less views. 145 * 146 * @var boolean 147 */ 148 protected $_tableExists = true; 149 150 /** 151 * The asset key for items in this table. It's usually something in the 152 * com_example.viewname format. They asset name will be this key appended 153 * with the item's ID, e.g. com_example.viewname.123 154 * 155 * @var string 156 */ 157 protected $_assetKey = ''; 158 159 /** 160 * The input data 161 * 162 * @var FOFInput 163 */ 164 protected $input = null; 165 166 /** 167 * Extended query including joins with other tables 168 * 169 * @var FOFDatabaseQuery 170 */ 171 protected $_queryJoin = null; 172 173 /** 174 * The prefix for the table class 175 * 176 * @var string 177 */ 178 protected $_tablePrefix = ''; 179 180 /** 181 * The known fields for this table 182 * 183 * @var array 184 */ 185 protected $knownFields = array(); 186 187 /** 188 * A list of table fields, keyed per table 189 * 190 * @var array 191 */ 192 protected static $tableFieldCache = array(); 193 194 /** 195 * A list of tables in the database 196 * 197 * @var array 198 */ 199 protected static $tableCache = array(); 200 201 /** 202 * An instance of FOFConfigProvider to provision configuration overrides 203 * 204 * @var FOFConfigProvider 205 */ 206 protected $configProvider = null; 207 208 /** 209 * FOFTableDispatcherBehavior for dealing with extra behaviors 210 * 211 * @var FOFTableDispatcherBehavior 212 */ 213 protected $tableDispatcher = null; 214 215 /** 216 * List of default behaviors to apply to the table 217 * 218 * @var array 219 */ 220 protected $default_behaviors = array('tags', 'assets'); 221 222 /** 223 * The relations object of the table. It's lazy-loaded by getRelations(). 224 * 225 * @var FOFTableRelations 226 */ 227 protected $_relations = null; 228 229 /** 230 * The configuration provider's key for this table, e.g. foobar.tables.bar for the #__foobar_bars table. This is set 231 * automatically by the constructor 232 * 233 * @var string 234 */ 235 protected $_configProviderKey = ''; 236 237 /** 238 * The content type of the table. Required if using tags or content history behaviour 239 * 240 * @var string 241 */ 242 protected $contentType = null; 243 244 /** 245 * Returns a static object instance of a particular table type 246 * 247 * @param string $type The table name 248 * @param string $prefix The prefix of the table class 249 * @param array $config Optional configuration variables 250 * 251 * @return FOFTable 252 */ 253 public static function getInstance($type, $prefix = 'JTable', $config = array()) 254 { 255 return self::getAnInstance($type, $prefix, $config); 256 } 257 258 /** 259 * Returns a static object instance of a particular table type 260 * 261 * @param string $type The table name 262 * @param string $prefix The prefix of the table class 263 * @param array $config Optional configuration variables 264 * 265 * @return FOFTable 266 */ 267 public static function &getAnInstance($type = null, $prefix = 'JTable', $config = array()) 268 { 269 // Make sure $config is an array 270 if (is_object($config)) 271 { 272 $config = (array) $config; 273 } 274 elseif (!is_array($config)) 275 { 276 $config = array(); 277 } 278 279 // Guess the component name 280 if (!array_key_exists('input', $config)) 281 { 282 $config['input'] = new FOFInput; 283 } 284 285 if ($config['input'] instanceof FOFInput) 286 { 287 $tmpInput = $config['input']; 288 } 289 else 290 { 291 $tmpInput = new FOFInput($config['input']); 292 } 293 294 $option = $tmpInput->getCmd('option', ''); 295 $tmpInput->set('option', $option); 296 $config['input'] = $tmpInput; 297 298 if (!in_array($prefix, array('Table', 'JTable'))) 299 { 300 preg_match('/(.*)Table$/', $prefix, $m); 301 $option = 'com_' . strtolower($m[1]); 302 } 303 304 if (array_key_exists('option', $config)) 305 { 306 $option = $config['option']; 307 } 308 309 $config['option'] = $option; 310 311 if (!array_key_exists('view', $config)) 312 { 313 $config['view'] = $config['input']->getCmd('view', 'cpanel'); 314 } 315 316 if (is_null($type)) 317 { 318 if ($prefix == 'JTable') 319 { 320 $prefix = 'Table'; 321 } 322 323 $type = $config['view']; 324 } 325 326 $type = preg_replace('/[^A-Z0-9_\.-]/i', '', $type); 327 $tableClass = $prefix . ucfirst($type); 328 329 $config['_table_type'] = $type; 330 $config['_table_class'] = $tableClass; 331 332 $configProvider = new FOFConfigProvider; 333 $configProviderKey = $option . '.views.' . FOFInflector::singularize($type) . '.config.'; 334 335 if (!array_key_exists($tableClass, self::$instances)) 336 { 337 if (!class_exists($tableClass)) 338 { 339 $componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($config['option']); 340 341 $searchPaths = array( 342 $componentPaths['main'] . '/tables', 343 $componentPaths['admin'] . '/tables' 344 ); 345 346 if (array_key_exists('tablepath', $config)) 347 { 348 array_unshift($searchPaths, $config['tablepath']); 349 } 350 351 $altPath = $configProvider->get($configProviderKey . 'table_path', null); 352 353 if ($altPath) 354 { 355 array_unshift($searchPaths, $componentPaths['admin'] . '/' . $altPath); 356 } 357 358 $filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem'); 359 360 $path = $filesystem->pathFind( 361 $searchPaths, strtolower($type) . '.php' 362 ); 363 364 if ($path) 365 { 366 require_once $path; 367 } 368 } 369 370 if (!class_exists($tableClass)) 371 { 372 $tableClass = 'FOFTable'; 373 } 374 375 $component = str_replace('com_', '', $config['option']); 376 $tbl_common = $component . '_'; 377 378 if (!array_key_exists('tbl', $config)) 379 { 380 $config['tbl'] = strtolower('#__' . $tbl_common . strtolower(FOFInflector::pluralize($type))); 381 } 382 383 $altTbl = $configProvider->get($configProviderKey . 'tbl', null); 384 385 if ($altTbl) 386 { 387 $config['tbl'] = $altTbl; 388 } 389 390 if (!array_key_exists('tbl_key', $config)) 391 { 392 $keyName = FOFInflector::singularize($type); 393 $config['tbl_key'] = strtolower($tbl_common . $keyName . '_id'); 394 } 395 396 $altTblKey = $configProvider->get($configProviderKey . 'tbl_key', null); 397 398 if ($altTblKey) 399 { 400 $config['tbl_key'] = $altTblKey; 401 } 402 403 if (!array_key_exists('db', $config)) 404 { 405 $config['db'] = FOFPlatform::getInstance()->getDbo(); 406 } 407 408 // Assign the correct table alias 409 if (array_key_exists('table_alias', $config)) 410 { 411 $table_alias = $config['table_alias']; 412 } 413 else 414 { 415 $configProviderTableAliasKey = $option . '.tables.' . FOFInflector::singularize($type) . '.tablealias'; 416 $table_alias = $configProvider->get($configProviderTableAliasKey, false ); 417 } 418 419 // Can we use the FOF cache? 420 if (!array_key_exists('use_table_cache', $config)) 421 { 422 $config['use_table_cache'] = FOFPlatform::getInstance()->isGlobalFOFCacheEnabled(); 423 } 424 425 $alt_use_table_cache = $configProvider->get($configProviderKey . 'use_table_cache', null); 426 427 if (!is_null($alt_use_table_cache)) 428 { 429 $config['use_table_cache'] = $alt_use_table_cache; 430 } 431 432 // Create a new table instance 433 $instance = new $tableClass($config['tbl'], $config['tbl_key'], $config['db'], $config); 434 $instance->setInput($tmpInput); 435 $instance->setTablePrefix($prefix); 436 $instance->setTableAlias($table_alias); 437 438 // Determine and set the asset key for this table 439 $assetKey = 'com_' . $component . '.' . strtolower(FOFInflector::singularize($type)); 440 $assetKey = $configProvider->get($configProviderKey . 'asset_key', $assetKey); 441 $instance->setAssetKey($assetKey); 442 443 if (array_key_exists('trigger_events', $config)) 444 { 445 $instance->setTriggerEvents($config['trigger_events']); 446 } 447 448 if (version_compare(JVERSION, '3.1', 'ge')) 449 { 450 if (array_key_exists('has_tags', $config)) 451 { 452 $instance->setHasTags($config['has_tags']); 453 } 454 455 $altHasTags = $configProvider->get($configProviderKey . 'has_tags', null); 456 457 if ($altHasTags) 458 { 459 $instance->setHasTags($altHasTags); 460 } 461 } 462 else 463 { 464 $instance->setHasTags(false); 465 } 466 467 $configProviderFieldmapKey = $option . '.tables.' . FOFInflector::singularize($type) . '.field'; 468 $aliases = $configProvider->get($configProviderFieldmapKey, $instance->_columnAlias); 469 $instance->_columnAlias = array_merge($instance->_columnAlias, $aliases); 470 471 self::$instances[$tableClass] = $instance; 472 } 473 474 return self::$instances[$tableClass]; 475 } 476 477 /** 478 * Force an instance inside class cache. Setting arguments to null nukes all or part of the cache 479 * 480 * @param string|null $key TableClass to replace. Set it to null to nuke the entire cache 481 * @param FOFTable|null $instance Instance to replace. Set it to null to nuke $key instances 482 * 483 * @return bool Did I correctly switch the instance? 484 */ 485 public static function forceInstance($key = null, $instance = null) 486 { 487 if(is_null($key)) 488 { 489 self::$instances = array(); 490 491 return true; 492 } 493 elseif($key && isset(self::$instances[$key])) 494 { 495 // I'm forcing an instance, but it's not a FOFTable, abort! abort! 496 if(!$instance || ($instance && $instance instanceof FOFTable)) 497 { 498 self::$instances[$key] = $instance; 499 500 return true; 501 } 502 } 503 504 return false; 505 } 506 507 /** 508 * Class Constructor. 509 * 510 * @param string $table Name of the database table to model. 511 * @param string $key Name of the primary key field in the table. 512 * @param FOFDatabaseDriver &$db Database driver 513 * @param array $config The configuration parameters array 514 */ 515 public function __construct($table, $key, &$db, $config = array()) 516 { 517 $this->_tbl = $table; 518 $this->_tbl_key = $key; 519 $this->_db = $db; 520 521 // Make sure the use FOF cache information is in the config 522 if (!array_key_exists('use_table_cache', $config)) 523 { 524 $config['use_table_cache'] = FOFPlatform::getInstance()->isGlobalFOFCacheEnabled(); 525 } 526 $this->config = $config; 527 528 // Load the configuration provider 529 $this->configProvider = new FOFConfigProvider; 530 531 // Load the behavior dispatcher 532 $this->tableDispatcher = new FOFTableDispatcherBehavior; 533 534 // Initialise the table properties. 535 536 if ($fields = $this->getTableFields()) 537 { 538 // Do I have anything joined? 539 $j_fields = $this->getQueryJoinFields(); 540 541 if ($j_fields) 542 { 543 $fields = array_merge($fields, $j_fields); 544 } 545 546 $this->setKnownFields(array_keys($fields), true); 547 $this->reset(); 548 } 549 else 550 { 551 $this->_tableExists = false; 552 } 553 554 // Get the input 555 if (array_key_exists('input', $config)) 556 { 557 if ($config['input'] instanceof FOFInput) 558 { 559 $this->input = $config['input']; 560 } 561 else 562 { 563 $this->input = new FOFInput($config['input']); 564 } 565 } 566 else 567 { 568 $this->input = new FOFInput; 569 } 570 571 // Set the $name/$_name variable 572 $component = $this->input->getCmd('option', 'com_foobar'); 573 574 if (array_key_exists('option', $config)) 575 { 576 $component = $config['option']; 577 } 578 579 $this->input->set('option', $component); 580 581 // Apply table behaviors 582 $type = explode("_", $this->_tbl); 583 $type = $type[count($type) - 1]; 584 585 $this->_configProviderKey = $component . '.tables.' . FOFInflector::singularize($type); 586 587 $configKey = $this->_configProviderKey . '.behaviors'; 588 589 if (isset($config['behaviors'])) 590 { 591 $behaviors = (array) $config['behaviors']; 592 } 593 elseif ($behaviors = $this->configProvider->get($configKey, null)) 594 { 595 $behaviors = explode(',', $behaviors); 596 } 597 else 598 { 599 $behaviors = $this->default_behaviors; 600 } 601 602 if (is_array($behaviors) && count($behaviors)) 603 { 604 foreach ($behaviors as $behavior) 605 { 606 $this->addBehavior($behavior); 607 } 608 } 609 610 // If we are tracking assets, make sure an access field exists and initially set the default. 611 $asset_id_field = $this->getColumnAlias('asset_id'); 612 $access_field = $this->getColumnAlias('access'); 613 614 if (in_array($asset_id_field, $this->getKnownFields())) 615 { 616 JLoader::import('joomla.access.rules'); 617 $this->_trackAssets = true; 618 } 619 620 // If the access property exists, set the default. 621 if (in_array($access_field, $this->getKnownFields())) 622 { 623 $this->$access_field = (int) FOFPlatform::getInstance()->getConfig()->get('access'); 624 } 625 626 $this->config = $config; 627 } 628 629 /** 630 * Replace the entire known fields array 631 * 632 * @param array $fields A simple array of known field names 633 * @param boolean $initialise Should we initialise variables to null? 634 * 635 * @return void 636 */ 637 public function setKnownFields($fields, $initialise = false) 638 { 639 $this->knownFields = $fields; 640 641 if ($initialise) 642 { 643 foreach ($this->knownFields as $field) 644 { 645 $this->$field = null; 646 } 647 } 648 } 649 650 /** 651 * Get the known fields array 652 * 653 * @return array 654 */ 655 public function getKnownFields() 656 { 657 return $this->knownFields; 658 } 659 660 /** 661 * Does the specified field exist? 662 * 663 * @param string $fieldName The field name to search (it's OK to use aliases) 664 * 665 * @return bool 666 */ 667 public function hasField($fieldName) 668 { 669 $search = $this->getColumnAlias($fieldName); 670 671 return in_array($search, $this->knownFields); 672 } 673 674 /** 675 * Add a field to the known fields array 676 * 677 * @param string $field The name of the field to add 678 * @param boolean $initialise Should we initialise the variable to null? 679 * 680 * @return void 681 */ 682 public function addKnownField($field, $initialise = false) 683 { 684 if (!in_array($field, $this->knownFields)) 685 { 686 $this->knownFields[] = $field; 687 688 if ($initialise) 689 { 690 $this->$field = null; 691 } 692 } 693 } 694 695 /** 696 * Remove a field from the known fields array 697 * 698 * @param string $field The name of the field to remove 699 * 700 * @return void 701 */ 702 public function removeKnownField($field) 703 { 704 if (in_array($field, $this->knownFields)) 705 { 706 $pos = array_search($field, $this->knownFields); 707 unset($this->knownFields[$pos]); 708 } 709 } 710 711 /** 712 * Adds a behavior to the table 713 * 714 * @param string $name The name of the behavior 715 * @param array $config Optional Behavior configuration 716 * 717 * @return boolean 718 */ 719 public function addBehavior($name, $config = array()) 720 { 721 // First look for ComponentnameTableViewnameBehaviorName (e.g. FoobarTableItemsBehaviorTags) 722 if (isset($this->config['option'])) 723 { 724 $option_name = str_replace('com_', '', $this->config['option']); 725 $behaviorClass = $this->config['_table_class'] . 'Behavior' . ucfirst(strtolower($name)); 726 727 if (class_exists($behaviorClass)) 728 { 729 $behavior = new $behaviorClass($this->tableDispatcher, $config); 730 731 return true; 732 } 733 734 // Then look for ComponentnameTableBehaviorName (e.g. FoobarTableBehaviorTags) 735 $option_name = str_replace('com_', '', $this->config['option']); 736 $behaviorClass = ucfirst($option_name) . 'TableBehavior' . ucfirst(strtolower($name)); 737 738 if (class_exists($behaviorClass)) 739 { 740 $behavior = new $behaviorClass($this->tableDispatcher, $config); 741 742 return true; 743 } 744 } 745 746 // Nothing found? Return false. 747 748 $behaviorClass = 'FOFTableBehavior' . ucfirst(strtolower($name)); 749 750 if (class_exists($behaviorClass) && $this->tableDispatcher) 751 { 752 $behavior = new $behaviorClass($this->tableDispatcher, $config); 753 754 return true; 755 } 756 757 return false; 758 } 759 760 /** 761 * Sets the events trigger switch state 762 * 763 * @param boolean $newState The new state of the switch (what else could it be?) 764 * 765 * @return void 766 */ 767 public function setTriggerEvents($newState = false) 768 { 769 $this->_trigger_events = $newState ? true : false; 770 } 771 772 /** 773 * Gets the events trigger switch state 774 * 775 * @return boolean 776 */ 777 public function getTriggerEvents() 778 { 779 return $this->_trigger_events; 780 } 781 782 /** 783 * Gets the has tags switch state 784 * 785 * @return bool 786 */ 787 public function hasTags() 788 { 789 return $this->_has_tags; 790 } 791 792 /** 793 * Sets the has tags switch state 794 * 795 * @param bool $newState 796 */ 797 public function setHasTags($newState = false) 798 { 799 $this->_has_tags = false; 800 801 // Tags are available only in 3.1+ 802 if (version_compare(JVERSION, '3.1', 'ge')) 803 { 804 $this->_has_tags = $newState ? true : false; 805 } 806 } 807 808 /** 809 * Set the class prefix 810 * 811 * @param string $prefix The prefix 812 */ 813 public function setTablePrefix($prefix) 814 { 815 $this->_tablePrefix = $prefix; 816 } 817 818 /** 819 * Sets fields to be skipped from automatic checks. 820 * 821 * @param array/string $skip Fields to be skipped by automatic checks 822 * 823 * @return void 824 */ 825 public function setSkipChecks($skip) 826 { 827 $this->_skipChecks = (array) $skip; 828 } 829 830 /** 831 * Method to load a row from the database by primary key and bind the fields 832 * to the FOFTable instance properties. 833 * 834 * @param mixed $keys An optional primary key value to load the row by, or an array of fields to match. If not 835 * set the instance property value is used. 836 * @param boolean $reset True to reset the default values before loading the new row. 837 * 838 * @return boolean True if successful. False if row not found. 839 * 840 * @throws RuntimeException 841 * @throws UnexpectedValueException 842 */ 843 public function load($keys = null, $reset = true) 844 { 845 if (!$this->_tableExists) 846 { 847 $result = false; 848 849 return $this->onAfterLoad($result); 850 } 851 852 if (empty($keys)) 853 { 854 // If empty, use the value of the current key 855 $keyName = $this->_tbl_key; 856 857 if (isset($this->$keyName)) 858 { 859 $keyValue = $this->$keyName; 860 } 861 else 862 { 863 $keyValue = null; 864 } 865 866 // If empty primary key there's is no need to load anything 867 868 if (empty($keyValue)) 869 { 870 $result = true; 871 872 return $this->onAfterLoad($result); 873 } 874 875 $keys = array($keyName => $keyValue); 876 } 877 elseif (!is_array($keys)) 878 { 879 // Load by primary key. 880 $keys = array($this->_tbl_key => $keys); 881 } 882 883 if ($reset) 884 { 885 $this->reset(); 886 } 887 888 // Initialise the query. 889 $query = $this->_db->getQuery(true); 890 $query->select($this->_tbl . '.*'); 891 $query->from($this->_tbl); 892 893 // Joined fields are ok, since I initialized them in the constructor 894 $fields = $this->getKnownFields(); 895 896 foreach ($keys as $field => $value) 897 { 898 // Check that $field is in the table. 899 900 if (!in_array($field, $fields)) 901 { 902 throw new UnexpectedValueException(sprintf('Missing field in table %s : %s.', $this->_tbl, $field)); 903 } 904 905 // Add the search tuple to the query. 906 $query->where($this->_db->qn($this->_tbl . '.' . $field) . ' = ' . $this->_db->q($value)); 907 } 908 909 // Do I have any joined table? 910 $j_query = $this->getQueryJoin(); 911 912 if ($j_query) 913 { 914 if ($j_query->select && $j_query->select->getElements()) 915 { 916 //$query->select($this->normalizeSelectFields($j_query->select->getElements(), true)); 917 $query->select($j_query->select->getElements()); 918 } 919 920 if ($j_query->join) 921 { 922 foreach ($j_query->join as $join) 923 { 924 $t = (string) $join; 925 926 // Joomla doesn't provide any access to the "name" variable, so I have to work with strings... 927 if (stripos($t, 'inner') !== false) 928 { 929 $query->innerJoin($join->getElements()); 930 } 931 elseif (stripos($t, 'left') !== false) 932 { 933 $query->leftJoin($join->getElements()); 934 } 935 elseif (stripos($t, 'right') !== false) 936 { 937 $query->rightJoin($join->getElements()); 938 } 939 elseif (stripos($t, 'outer') !== false) 940 { 941 $query->outerJoin($join->getElements()); 942 } 943 } 944 } 945 } 946 947 $this->_db->setQuery($query); 948 949 $row = $this->_db->loadAssoc(); 950 951 // Check that we have a result. 952 if (empty($row)) 953 { 954 $result = false; 955 956 return $this->onAfterLoad($result); 957 } 958 959 // Bind the object with the row and return. 960 $result = $this->bind($row); 961 962 $this->onAfterLoad($result); 963 964 return $result; 965 } 966 967 /** 968 * Based on fields properties (nullable column), checks if the field is required or not 969 * 970 * @return boolean 971 */ 972 public function check() 973 { 974 if (!$this->_autoChecks) 975 { 976 return true; 977 } 978 979 $fields = $this->getTableFields(); 980 981 // No fields? Why in the hell am I here? 982 if(!$fields) 983 { 984 return false; 985 } 986 987 $result = true; 988 $known = $this->getKnownFields(); 989 $skipFields[] = $this->_tbl_key; 990 991 if(in_array($this->getColumnAlias('title'), $known) 992 && in_array($this->getColumnAlias('slug'), $known)) $skipFields[] = $this->getColumnAlias('slug'); 993 if(in_array($this->getColumnAlias('hits'), $known)) $skipFields[] = $this->getColumnAlias('hits'); 994 if(in_array($this->getColumnAlias('created_on'), $known)) $skipFields[] = $this->getColumnAlias('created_on'); 995 if(in_array($this->getColumnAlias('created_by'), $known)) $skipFields[] = $this->getColumnAlias('created_by'); 996 if(in_array($this->getColumnAlias('modified_on'), $known)) $skipFields[] = $this->getColumnAlias('modified_on'); 997 if(in_array($this->getColumnAlias('modified_by'), $known)) $skipFields[] = $this->getColumnAlias('modified_by'); 998 if(in_array($this->getColumnAlias('locked_by'), $known)) $skipFields[] = $this->getColumnAlias('locked_by'); 999 if(in_array($this->getColumnAlias('locked_on'), $known)) $skipFields[] = $this->getColumnAlias('locked_on'); 1000 1001 // Let's merge it with custom skips 1002 $skipFields = array_merge($skipFields, $this->_skipChecks); 1003 1004 foreach ($fields as $field) 1005 { 1006 $fieldName = $field->Field; 1007 1008 if (empty($fieldName)) 1009 { 1010 $fieldName = $field->column_name; 1011 } 1012 1013 // Field is not nullable but it's null, set error 1014 1015 if ($field->Null == 'NO' && $this->$fieldName == '' && !in_array($fieldName, $skipFields)) 1016 { 1017 $text = str_replace('#__', 'COM_', $this->getTableName()) . '_ERR_' . $fieldName; 1018 $this->setError(JText::_(strtoupper($text))); 1019 $result = false; 1020 } 1021 } 1022 1023 return $result; 1024 } 1025 1026 /** 1027 * Method to reset class properties to the defaults set in the class 1028 * definition. It will ignore the primary key as well as any private class 1029 * properties. 1030 * 1031 * @return void 1032 */ 1033 public function reset() 1034 { 1035 if (!$this->onBeforeReset()) 1036 { 1037 return false; 1038 } 1039 1040 // Get the default values for the class from the table. 1041 $fields = $this->getTableFields(); 1042 $j_fields = $this->getQueryJoinFields(); 1043 1044 if ($j_fields) 1045 { 1046 $fields = array_merge($fields, $j_fields); 1047 } 1048 1049 if (is_array($fields) && !empty($fields)) 1050 { 1051 foreach ($fields as $k => $v) 1052 { 1053 // If the property is not the primary key or private, reset it. 1054 if ($k != $this->_tbl_key && (strpos($k, '_') !== 0)) 1055 { 1056 $this->$k = $v->Default; 1057 } 1058 } 1059 1060 if (!$this->onAfterReset()) 1061 { 1062 return false; 1063 } 1064 } 1065 } 1066 1067 /** 1068 * Clones the current object, after resetting it 1069 * 1070 * @return static 1071 */ 1072 public function getClone() 1073 { 1074 $clone = clone $this; 1075 $clone->reset(); 1076 1077 $key = $this->getKeyName(); 1078 $clone->$key = null; 1079 1080 return $clone; 1081 } 1082 1083 /** 1084 * Generic check for whether dependencies exist for this object in the db schema 1085 * 1086 * @param integer $oid The primary key of the record to delete 1087 * @param array $joins Any joins to foreign table, used to determine if dependent records exist 1088 * 1089 * @return boolean True if the record can be deleted 1090 */ 1091 public function canDelete($oid = null, $joins = null) 1092 { 1093 $k = $this->_tbl_key; 1094 1095 if ($oid) 1096 { 1097 $this->$k = intval($oid); 1098 } 1099 1100 if (is_array($joins)) 1101 { 1102 $db = $this->_db; 1103 $query = $db->getQuery(true) 1104 ->select($db->qn('master') . '.' . $db->qn($k)) 1105 ->from($db->qn($this->_tbl) . ' AS ' . $db->qn('master')); 1106 $tableNo = 0; 1107 1108 foreach ($joins as $table) 1109 { 1110 $tableNo++; 1111 $query->select( 1112 array( 1113 'COUNT(DISTINCT ' . $db->qn('t' . $tableNo) . '.' . $db->qn($table['idfield']) . ') AS ' . $db->qn($table['idalias']) 1114 ) 1115 ); 1116 $query->join('LEFT', $db->qn($table['name']) . 1117 ' AS ' . $db->qn('t' . $tableNo) . 1118 ' ON ' . $db->qn('t' . $tableNo) . '.' . $db->qn($table['joinfield']) . 1119 ' = ' . $db->qn('master') . '.' . $db->qn($k) 1120 ); 1121 } 1122 1123 $query->where($db->qn('master') . '.' . $db->qn($k) . ' = ' . $db->q($this->$k)); 1124 $query->group($db->qn('master') . '.' . $db->qn($k)); 1125 $this->_db->setQuery((string) $query); 1126 1127 if (version_compare(JVERSION, '3.0', 'ge')) 1128 { 1129 try 1130 { 1131 $obj = $this->_db->loadObject(); 1132 } 1133 catch (Exception $e) 1134 { 1135 $this->setError($e->getMessage()); 1136 } 1137 } 1138 else 1139 { 1140 if (!$obj = $this->_db->loadObject()) 1141 { 1142 $this->setError($this->_db->getErrorMsg()); 1143 1144 return false; 1145 } 1146 } 1147 1148 $msg = array(); 1149 $i = 0; 1150 1151 foreach ($joins as $table) 1152 { 1153 $k = $table['idalias']; 1154 1155 if ($obj->$k > 0) 1156 { 1157 $msg[] = JText::_($table['label']); 1158 } 1159 1160 $i++; 1161 } 1162 1163 if (count($msg)) 1164 { 1165 $option = $this->input->getCmd('option', 'com_foobar'); 1166 $comName = str_replace('com_', '', $option); 1167 $tview = str_replace('#__' . $comName . '_', '', $this->_tbl); 1168 $prefix = $option . '_' . $tview . '_NODELETE_'; 1169 1170 foreach ($msg as $key) 1171 { 1172 $this->setError(JText::_($prefix . $key)); 1173 } 1174 1175 return false; 1176 } 1177 else 1178 { 1179 return true; 1180 } 1181 } 1182 1183 return true; 1184 } 1185 1186 /** 1187 * Method to bind an associative array or object to the FOFTable instance.This 1188 * method only binds properties that are publicly accessible and optionally 1189 * takes an array of properties to ignore when binding. 1190 * 1191 * @param mixed $src An associative array or object to bind to the FOFTable instance. 1192 * @param mixed $ignore An optional array or space separated list of properties to ignore while binding. 1193 * 1194 * @return boolean True on success. 1195 * 1196 * @throws InvalidArgumentException 1197 */ 1198 public function bind($src, $ignore = array()) 1199 { 1200 if (!$this->onBeforeBind($src)) 1201 { 1202 return false; 1203 } 1204 1205 // If the source value is not an array or object return false. 1206 if (!is_object($src) && !is_array($src)) 1207 { 1208 throw new InvalidArgumentException(sprintf('%s::bind(*%s*)', get_class($this), gettype($src))); 1209 } 1210 1211 // If the source value is an object, get its accessible properties. 1212 if (is_object($src)) 1213 { 1214 $src = get_object_vars($src); 1215 } 1216 1217 // If the ignore value is a string, explode it over spaces. 1218 if (!is_array($ignore)) 1219 { 1220 $ignore = explode(' ', $ignore); 1221 } 1222 1223 // Bind the source value, excluding the ignored fields. 1224 foreach ($this->getKnownFields() as $k) 1225 { 1226 // Only process fields not in the ignore array. 1227 if (!in_array($k, $ignore)) 1228 { 1229 if (isset($src[$k])) 1230 { 1231 $this->$k = $src[$k]; 1232 } 1233 } 1234 } 1235 1236 $result = $this->onAfterBind($src); 1237 1238 return $result; 1239 } 1240 1241 /** 1242 * Method to store a row in the database from the FOFTable instance properties. 1243 * If a primary key value is set the row with that primary key value will be 1244 * updated with the instance property values. If no primary key value is set 1245 * a new row will be inserted into the database with the properties from the 1246 * FOFTable instance. 1247 * 1248 * @param boolean $updateNulls True to update fields even if they are null. 1249 * 1250 * @return boolean True on success. 1251 */ 1252 public function store($updateNulls = false) 1253 { 1254 if (!$this->onBeforeStore($updateNulls)) 1255 { 1256 return false; 1257 } 1258 1259 $k = $this->_tbl_key; 1260 1261 if ($this->$k == 0) 1262 { 1263 $this->$k = null; 1264 } 1265 1266 // Create the object used for inserting/updating data to the database 1267 $fields = $this->getTableFields(); 1268 $properties = $this->getKnownFields(); 1269 $keys = array(); 1270 1271 foreach ($properties as $property) 1272 { 1273 // 'input' property is a reserved name 1274 1275 if (isset($fields[$property])) 1276 { 1277 $keys[] = $property; 1278 } 1279 } 1280 1281 $updateObject = array(); 1282 foreach ($keys as $key) 1283 { 1284 $updateObject[$key] = $this->$key; 1285 } 1286 $updateObject = (object)$updateObject; 1287 1288 /** 1289 * While the documentation for update/insertObject and execute() say they return a boolean, 1290 * not all of the implemtnations. Depending on the version of J! and the specific driver, 1291 * they may return a database object, or boolean, or a mix, or toss an exception. So try/catch, 1292 * and test for false. 1293 */ 1294 1295 try 1296 { 1297 // If a primary key exists update the object, otherwise insert it. 1298 if ($this->$k) 1299 { 1300 $result = $this->_db->updateObject($this->_tbl, $updateObject, $this->_tbl_key, $updateNulls); 1301 } 1302 else 1303 { 1304 $result = $this->_db->insertObject($this->_tbl, $updateObject, $this->_tbl_key); 1305 } 1306 1307 if ($result === false) 1308 { 1309 $this->setError($this->_db->getErrorMsg()); 1310 1311 return false; 1312 } 1313 } 1314 catch (Exception $e) 1315 { 1316 $this->setError($e->getMessage()); 1317 } 1318 1319 $this->bind($updateObject); 1320 1321 if ($this->_locked) 1322 { 1323 $this->_unlock(); 1324 } 1325 1326 $result = $this->onAfterStore(); 1327 1328 return $result; 1329 } 1330 1331 /** 1332 * Method to move a row in the ordering sequence of a group of rows defined by an SQL WHERE clause. 1333 * Negative numbers move the row up in the sequence and positive numbers move it down. 1334 * 1335 * @param integer $delta The direction and magnitude to move the row in the ordering sequence. 1336 * @param string $where WHERE clause to use for limiting the selection of rows to compact the 1337 * ordering values. 1338 * 1339 * @return mixed Boolean True on success. 1340 * 1341 * @throws UnexpectedValueException 1342 */ 1343 public function move($delta, $where = '') 1344 { 1345 if (!$this->onBeforeMove($delta, $where)) 1346 { 1347 return false; 1348 } 1349 1350 // If there is no ordering field set an error and return false. 1351 $ordering_field = $this->getColumnAlias('ordering'); 1352 1353 if (!in_array($ordering_field, $this->getKnownFields())) 1354 { 1355 throw new UnexpectedValueException(sprintf('%s does not support ordering.', $this->_tbl)); 1356 } 1357 1358 // If the change is none, do nothing. 1359 if (empty($delta)) 1360 { 1361 $result = $this->onAfterMove(); 1362 1363 return $result; 1364 } 1365 1366 $k = $this->_tbl_key; 1367 $row = null; 1368 $query = $this->_db->getQuery(true); 1369 1370 // If the table is not loaded, return false 1371 if (empty($this->$k)) 1372 { 1373 return false; 1374 } 1375 1376 // Select the primary key and ordering values from the table. 1377 $query->select(array($this->_db->qn($this->_tbl_key), $this->_db->qn($ordering_field))); 1378 $query->from($this->_tbl); 1379 1380 // If the movement delta is negative move the row up. 1381 1382 if ($delta < 0) 1383 { 1384 $query->where($this->_db->qn($ordering_field) . ' < ' . $this->_db->q((int) $this->$ordering_field)); 1385 $query->order($this->_db->qn($ordering_field) . ' DESC'); 1386 } 1387 1388 // If the movement delta is positive move the row down. 1389 1390 elseif ($delta > 0) 1391 { 1392 $query->where($this->_db->qn($ordering_field) . ' > ' . $this->_db->q((int) $this->$ordering_field)); 1393 $query->order($this->_db->qn($ordering_field) . ' ASC'); 1394 } 1395 1396 // Add the custom WHERE clause if set. 1397 1398 if ($where) 1399 { 1400 $query->where($where); 1401 } 1402 1403 // Select the first row with the criteria. 1404 $this->_db->setQuery($query, 0, 1); 1405 $row = $this->_db->loadObject(); 1406 1407 // If a row is found, move the item. 1408 1409 if (!empty($row)) 1410 { 1411 // Update the ordering field for this instance to the row's ordering value. 1412 $query = $this->_db->getQuery(true); 1413 $query->update($this->_tbl); 1414 $query->set($this->_db->qn($ordering_field) . ' = ' . $this->_db->q((int) $row->$ordering_field)); 1415 $query->where($this->_tbl_key . ' = ' . $this->_db->q($this->$k)); 1416 $this->_db->setQuery($query); 1417 $this->_db->execute(); 1418 1419 // Update the ordering field for the row to this instance's ordering value. 1420 $query = $this->_db->getQuery(true); 1421 $query->update($this->_tbl); 1422 $query->set($this->_db->qn($ordering_field) . ' = ' . $this->_db->q((int) $this->$ordering_field)); 1423 $query->where($this->_tbl_key . ' = ' . $this->_db->q($row->$k)); 1424 $this->_db->setQuery($query); 1425 $this->_db->execute(); 1426 1427 // Update the instance value. 1428 $this->$ordering_field = $row->$ordering_field; 1429 } 1430 else 1431 { 1432 // Update the ordering field for this instance. 1433 $query = $this->_db->getQuery(true); 1434 $query->update($this->_tbl); 1435 $query->set($this->_db->qn($ordering_field) . ' = ' . $this->_db->q((int) $this->$ordering_field)); 1436 $query->where($this->_tbl_key . ' = ' . $this->_db->q($this->$k)); 1437 $this->_db->setQuery($query); 1438 $this->_db->execute(); 1439 } 1440 1441 $result = $this->onAfterMove(); 1442 1443 return $result; 1444 } 1445 1446 /** 1447 * Change the ordering of the records of the table 1448 * 1449 * @param string $where The WHERE clause of the SQL used to fetch the order 1450 * 1451 * @return boolean True is successful 1452 * 1453 * @throws UnexpectedValueException 1454 */ 1455 public function reorder($where = '') 1456 { 1457 if (!$this->onBeforeReorder($where)) 1458 { 1459 return false; 1460 } 1461 1462 // If there is no ordering field set an error and return false. 1463 1464 $order_field = $this->getColumnAlias('ordering'); 1465 1466 if (!in_array($order_field, $this->getKnownFields())) 1467 { 1468 throw new UnexpectedValueException(sprintf('%s does not support ordering.', $this->_tbl_key)); 1469 } 1470 1471 $k = $this->_tbl_key; 1472 1473 // Get the primary keys and ordering values for the selection. 1474 $query = $this->_db->getQuery(true); 1475 $query->select($this->_tbl_key . ', ' . $this->_db->qn($order_field)); 1476 $query->from($this->_tbl); 1477 $query->where($this->_db->qn($order_field) . ' >= ' . $this->_db->q(0)); 1478 $query->order($this->_db->qn($order_field)); 1479 1480 // Setup the extra where and ordering clause data. 1481 1482 if ($where) 1483 { 1484 $query->where($where); 1485 } 1486 1487 $this->_db->setQuery($query); 1488 $rows = $this->_db->loadObjectList(); 1489 1490 // Compact the ordering values. 1491 1492 foreach ($rows as $i => $row) 1493 { 1494 // Make sure the ordering is a positive integer. 1495 1496 if ($row->$order_field >= 0) 1497 { 1498 // Only update rows that are necessary. 1499 1500 if ($row->$order_field != $i + 1) 1501 { 1502 // Update the row ordering field. 1503 $query = $this->_db->getQuery(true); 1504 $query->update($this->_tbl); 1505 $query->set($this->_db->qn($order_field) . ' = ' . $this->_db->q($i + 1)); 1506 $query->where($this->_tbl_key . ' = ' . $this->_db->q($row->$k)); 1507 $this->_db->setQuery($query); 1508 $this->_db->execute(); 1509 } 1510 } 1511 } 1512 1513 $result = $this->onAfterReorder(); 1514 1515 return $result; 1516 } 1517 1518 /** 1519 * Check out (lock) a record 1520 * 1521 * @param integer $userId The locking user's ID 1522 * @param integer $oid The primary key value of the record to lock 1523 * 1524 * @return boolean True on success 1525 */ 1526 public function checkout($userId, $oid = null) 1527 { 1528 $fldLockedBy = $this->getColumnAlias('locked_by'); 1529 $fldLockedOn = $this->getColumnAlias('locked_on'); 1530 1531 if (!(in_array($fldLockedBy, $this->getKnownFields()) 1532 || in_array($fldLockedOn, $this->getKnownFields()))) 1533 { 1534 return true; 1535 } 1536 1537 $k = $this->_tbl_key; 1538 1539 if ($oid !== null) 1540 { 1541 $this->$k = $oid; 1542 } 1543 1544 // No primary key defined, stop here 1545 if (!$this->$k) 1546 { 1547 return false; 1548 } 1549 1550 $date = FOFPlatform::getInstance()->getDate(); 1551 1552 if (method_exists($date, 'toSql')) 1553 { 1554 $time = $date->toSql(); 1555 } 1556 else 1557 { 1558 $time = $date->toMySQL(); 1559 } 1560 1561 1562 $query = $this->_db->getQuery(true) 1563 ->update($this->_db->qn($this->_tbl)) 1564 ->set( 1565 array( 1566 $this->_db->qn($fldLockedBy) . ' = ' . $this->_db->q((int) $userId), 1567 $this->_db->qn($fldLockedOn) . ' = ' . $this->_db->q($time) 1568 ) 1569 ) 1570 ->where($this->_db->qn($this->_tbl_key) . ' = ' . $this->_db->q($this->$k)); 1571 $this->_db->setQuery((string) $query); 1572 1573 $this->$fldLockedBy = $userId; 1574 $this->$fldLockedOn = $time; 1575 1576 return $this->_db->execute(); 1577 } 1578 1579 /** 1580 * Check in (unlock) a record 1581 * 1582 * @param integer $oid The primary key value of the record to unlock 1583 * 1584 * @return boolean True on success 1585 */ 1586 public function checkin($oid = null) 1587 { 1588 $fldLockedBy = $this->getColumnAlias('locked_by'); 1589 $fldLockedOn = $this->getColumnAlias('locked_on'); 1590 1591 if (!(in_array($fldLockedBy, $this->getKnownFields()) 1592 || in_array($fldLockedOn, $this->getKnownFields()))) 1593 { 1594 return true; 1595 } 1596 1597 $k = $this->_tbl_key; 1598 1599 if ($oid !== null) 1600 { 1601 $this->$k = $oid; 1602 } 1603 1604 if ($this->$k == null) 1605 { 1606 return false; 1607 } 1608 1609 $query = $this->_db->getQuery(true) 1610 ->update($this->_db->qn($this->_tbl)) 1611 ->set( 1612 array( 1613 $this->_db->qn($fldLockedBy) . ' = 0', 1614 $this->_db->qn($fldLockedOn) . ' = ' . $this->_db->q($this->_db->getNullDate()) 1615 ) 1616 ) 1617 ->where($this->_db->qn($this->_tbl_key) . ' = ' . $this->_db->q($this->$k)); 1618 $this->_db->setQuery((string) $query); 1619 1620 $this->$fldLockedBy = 0; 1621 $this->$fldLockedOn = ''; 1622 1623 return $this->_db->execute(); 1624 } 1625 1626 /** 1627 * Is a record locked? 1628 * 1629 * @param integer $with The userid to preform the match with. If an item is checked 1630 * out by this user the function will return false. 1631 * @param integer $unused_against Junk inherited from JTable; ignore 1632 * 1633 * @throws UnexpectedValueException 1634 * 1635 * @return boolean True if the record is locked by another user 1636 */ 1637 public function isCheckedOut($with = 0, $unused_against = null) 1638 { 1639 $against = null; 1640 $fldLockedBy = $this->getColumnAlias('locked_by'); 1641 1642 $k = $this->_tbl_key; 1643 1644 // If no primary key is given, return false. 1645 1646 if ($this->$k === null) 1647 { 1648 throw new UnexpectedValueException('Null primary key not allowed.'); 1649 } 1650 1651 if (isset($this) && is_a($this, 'FOFTable') && !$against) 1652 { 1653 $against = $this->get($fldLockedBy); 1654 } 1655 1656 // Item is not checked out, or being checked out by the same user 1657 1658 if (!$against || $against == $with) 1659 { 1660 return false; 1661 } 1662 1663 $session = JTable::getInstance('session'); 1664 1665 return $session->exists($against); 1666 } 1667 1668 /** 1669 * Copy (duplicate) one or more records 1670 * 1671 * @param integer|array $cid The primary key value (or values) or the record(s) to copy 1672 * 1673 * @return boolean True on success 1674 */ 1675 public function copy($cid = null) 1676 { 1677 //We have to cast the id as array, or the helper function will return an empty set 1678 if($cid) 1679 { 1680 $cid = (array) $cid; 1681 } 1682 1683 FOFUtilsArray::toInteger($cid); 1684 $k = $this->_tbl_key; 1685 1686 if (count($cid) < 1) 1687 { 1688 if ($this->$k) 1689 { 1690 $cid = array($this->$k); 1691 } 1692 else 1693 { 1694 $this->setError("No items selected."); 1695 1696 return false; 1697 } 1698 } 1699 1700 $created_by = $this->getColumnAlias('created_by'); 1701 $created_on = $this->getColumnAlias('created_on'); 1702 $modified_by = $this->getColumnAlias('modified_by'); 1703 $modified_on = $this->getColumnAlias('modified_on'); 1704 1705 $locked_byName = $this->getColumnAlias('locked_by'); 1706 $checkin = in_array($locked_byName, $this->getKnownFields()); 1707 1708 foreach ($cid as $item) 1709 { 1710 // Prevent load with id = 0 1711 1712 if (!$item) 1713 { 1714 continue; 1715 } 1716 1717 $this->load($item); 1718 1719 if ($checkin) 1720 { 1721 // We're using the checkin and the record is used by someone else 1722 1723 if ($this->isCheckedOut($item)) 1724 { 1725 continue; 1726 } 1727 } 1728 1729 if (!$this->onBeforeCopy($item)) 1730 { 1731 continue; 1732 } 1733 1734 $this->$k = null; 1735 $this->$created_by = null; 1736 $this->$created_on = null; 1737 $this->$modified_on = null; 1738 $this->$modified_by = null; 1739 1740 // Let's fire the event only if everything is ok 1741 if ($this->store()) 1742 { 1743 $this->onAfterCopy($item); 1744 } 1745 1746 $this->reset(); 1747 } 1748 1749 return true; 1750 } 1751 1752 /** 1753 * Publish or unpublish records 1754 * 1755 * @param integer|array $cid The primary key value(s) of the item(s) to publish/unpublish 1756 * @param integer $publish 1 to publish an item, 0 to unpublish 1757 * @param integer $user_id The user ID of the user (un)publishing the item. 1758 * 1759 * @return boolean True on success, false on failure (e.g. record is locked) 1760 */ 1761 public function publish($cid = null, $publish = 1, $user_id = 0) 1762 { 1763 $enabledName = $this->getColumnAlias('enabled'); 1764 $locked_byName = $this->getColumnAlias('locked_by'); 1765 1766 // Mhm... you called the publish method on a table without publish support... 1767 if(!in_array($enabledName, $this->getKnownFields())) 1768 { 1769 return false; 1770 } 1771 1772 //We have to cast the id as array, or the helper function will return an empty set 1773 if($cid) 1774 { 1775 $cid = (array) $cid; 1776 } 1777 1778 FOFUtilsArray::toInteger($cid); 1779 $user_id = (int) $user_id; 1780 $publish = (int) $publish; 1781 $k = $this->_tbl_key; 1782 1783 if (count($cid) < 1) 1784 { 1785 if ($this->$k) 1786 { 1787 $cid = array($this->$k); 1788 } 1789 else 1790 { 1791 $this->setError("No items selected."); 1792 1793 return false; 1794 } 1795 } 1796 1797 if (!$this->onBeforePublish($cid, $publish)) 1798 { 1799 return false; 1800 } 1801 1802 $query = $this->_db->getQuery(true) 1803 ->update($this->_db->qn($this->_tbl)) 1804 ->set($this->_db->qn($enabledName) . ' = ' . (int) $publish); 1805 1806 $checkin = in_array($locked_byName, $this->getKnownFields()); 1807 1808 if ($checkin) 1809 { 1810 $query->where( 1811 ' (' . $this->_db->qn($locked_byName) . 1812 ' = 0 OR ' . $this->_db->qn($locked_byName) . ' = ' . (int) $user_id . ')', 'AND' 1813 ); 1814 } 1815 1816 // TODO Rewrite this statment using IN. Check if it work in SQLServer and PostgreSQL 1817 $cids = $this->_db->qn($k) . ' = ' . implode(' OR ' . $this->_db->qn($k) . ' = ', $cid); 1818 1819 $query->where('(' . $cids . ')'); 1820 1821 $this->_db->setQuery((string) $query); 1822 1823 if (version_compare(JVERSION, '3.0', 'ge')) 1824 { 1825 try 1826 { 1827 $this->_db->execute(); 1828 } 1829 catch (Exception $e) 1830 { 1831 $this->setError($e->getMessage()); 1832 } 1833 } 1834 else 1835 { 1836 if (!$this->_db->execute()) 1837 { 1838 $this->setError($this->_db->getErrorMsg()); 1839 1840 return false; 1841 } 1842 } 1843 1844 if (count($cid) == 1 && $checkin) 1845 { 1846 if ($this->_db->getAffectedRows() == 1) 1847 { 1848 $this->checkin($cid[0]); 1849 1850 if ($this->$k == $cid[0]) 1851 { 1852 $this->$enabledName = $publish; 1853 } 1854 } 1855 } 1856 1857 $this->setError(''); 1858 1859 return true; 1860 } 1861 1862 /** 1863 * Delete a record 1864 * 1865 * @param integer $oid The primary key value of the item to delete 1866 * 1867 * @throws UnexpectedValueException 1868 * 1869 * @return boolean True on success 1870 */ 1871 public function delete($oid = null) 1872 { 1873 if ($oid) 1874 { 1875 $this->load($oid); 1876 } 1877 1878 $k = $this->_tbl_key; 1879 $pk = (!$oid) ? $this->$k : $oid; 1880 1881 // If no primary key is given, return false. 1882 if (!$pk) 1883 { 1884 throw new UnexpectedValueException('Null primary key not allowed.'); 1885 } 1886 1887 // Execute the logic only if I have a primary key, otherwise I could have weird results 1888 if (!$this->onBeforeDelete($oid)) 1889 { 1890 return false; 1891 } 1892 1893 // Delete the row by primary key. 1894 $query = $this->_db->getQuery(true); 1895 $query->delete(); 1896 $query->from($this->_tbl); 1897 $query->where($this->_tbl_key . ' = ' . $this->_db->q($pk)); 1898 $this->_db->setQuery($query); 1899 1900 $this->_db->execute(); 1901 1902 $result = $this->onAfterDelete($oid); 1903 1904 return $result; 1905 } 1906 1907 /** 1908 * Register a hit on a record 1909 * 1910 * @param integer $oid The primary key value of the record 1911 * @param boolean $log Should I log the hit? 1912 * 1913 * @return boolean True on success 1914 */ 1915 public function hit($oid = null, $log = false) 1916 { 1917 if (!$this->onBeforeHit($oid, $log)) 1918 { 1919 return false; 1920 } 1921 1922 // If there is no hits field, just return true. 1923 $hits_field = $this->getColumnAlias('hits'); 1924 1925 if (!in_array($hits_field, $this->getKnownFields())) 1926 { 1927 return true; 1928 } 1929 1930 $k = $this->_tbl_key; 1931 $pk = ($oid) ? $oid : $this->$k; 1932 1933 // If no primary key is given, return false. 1934 if (!$pk) 1935 { 1936 $result = false; 1937 } 1938 else 1939 { 1940 // Check the row in by primary key. 1941 $query = $this->_db->getQuery(true) 1942 ->update($this->_tbl) 1943 ->set($this->_db->qn($hits_field) . ' = (' . $this->_db->qn($hits_field) . ' + 1)') 1944 ->where($this->_tbl_key . ' = ' . $this->_db->q($pk)); 1945 1946 $this->_db->setQuery($query)->execute(); 1947 1948 // In order to update the table object, I have to load the table 1949 if(!$this->$k) 1950 { 1951 $query = $this->_db->getQuery(true) 1952 ->select($this->_db->qn($hits_field)) 1953 ->from($this->_db->qn($this->_tbl)) 1954 ->where($this->_db->qn($this->_tbl_key) . ' = ' . $this->_db->q($pk)); 1955 1956 $this->$hits_field = $this->_db->setQuery($query)->loadResult(); 1957 } 1958 else 1959 { 1960 // Set table values in the object. 1961 $this->$hits_field++; 1962 } 1963 1964 $result = true; 1965 } 1966 1967 if ($result) 1968 { 1969 $result = $this->onAfterHit($oid); 1970 } 1971 1972 return $result; 1973 } 1974 1975 /** 1976 * Export the item as a CSV line 1977 * 1978 * @param string $separator CSV separator. Tip: use "\t" to get a TSV file instead. 1979 * 1980 * @return string The CSV line 1981 */ 1982 public function toCSV($separator = ',') 1983 { 1984 $csv = array(); 1985 1986 foreach (get_object_vars($this) as $k => $v) 1987 { 1988 if (!in_array($k, $this->getKnownFields())) 1989 { 1990 continue; 1991 } 1992 1993 $csv[] = '"' . str_replace('"', '""', $v) . '"'; 1994 } 1995 1996 $csv = implode($separator, $csv); 1997 1998 return $csv; 1999 } 2000 2001 /** 2002 * Exports the table in array format 2003 * 2004 * @return array 2005 */ 2006 public function getData() 2007 { 2008 $ret = array(); 2009 2010 foreach (get_object_vars($this) as $k => $v) 2011 { 2012 if (!in_array($k, $this->getKnownFields())) 2013 { 2014 continue; 2015 } 2016 2017 $ret[$k] = $v; 2018 } 2019 2020 return $ret; 2021 } 2022 2023 /** 2024 * Get the header for exporting item list to CSV 2025 * 2026 * @param string $separator CSV separator. Tip: use "\t" to get a TSV file instead. 2027 * 2028 * @return string The CSV file's header 2029 */ 2030 public function getCSVHeader($separator = ',') 2031 { 2032 $csv = array(); 2033 2034 foreach (get_object_vars($this) as $k => $v) 2035 { 2036 if (!in_array($k, $this->getKnownFields())) 2037 { 2038 continue; 2039 } 2040 2041 $csv[] = '"' . str_replace('"', '\"', $k) . '"'; 2042 } 2043 2044 $csv = implode($separator, $csv); 2045 2046 return $csv; 2047 } 2048 2049 /** 2050 * Get the columns from a database table. 2051 * 2052 * @param string $tableName Table name. If null current table is used 2053 * 2054 * @return mixed An array of the field names, or false if an error occurs. 2055 */ 2056 public function getTableFields($tableName = null) 2057 { 2058 // Should I load the cached data? 2059 $useCache = array_key_exists('use_table_cache', $this->config) ? $this->config['use_table_cache'] : false; 2060 2061 // Make sure we have a list of tables in this db 2062 2063 if (empty(self::$tableCache)) 2064 { 2065 if ($useCache) 2066 { 2067 // Try to load table cache from a cache file 2068 $cacheData = FOFPlatform::getInstance()->getCache('tables', null); 2069 2070 // Unserialise the cached data, or set the table cache to empty 2071 // if the cache data wasn't loaded. 2072 if (!is_null($cacheData)) 2073 { 2074 self::$tableCache = json_decode($cacheData, true); 2075 } 2076 else 2077 { 2078 self::$tableCache = array(); 2079 } 2080 } 2081 2082 // This check is true if the cache data doesn't exist / is not loaded 2083 if (empty(self::$tableCache)) 2084 { 2085 self::$tableCache = $this->_db->getTableList(); 2086 2087 if ($useCache) 2088 { 2089 FOFPlatform::getInstance()->setCache('tables', json_encode(self::$tableCache)); 2090 } 2091 } 2092 } 2093 2094 // Make sure the cached table fields cache is loaded 2095 if (empty(self::$tableFieldCache)) 2096 { 2097 if ($useCache) 2098 { 2099 // Try to load table cache from a cache file 2100 $cacheData = FOFPlatform::getInstance()->getCache('tablefields', null); 2101 2102 // Unserialise the cached data, or set to empty if the cache 2103 // data wasn't loaded. 2104 if (!is_null($cacheData)) 2105 { 2106 $decoded = json_decode($cacheData, true); 2107 $tableCache = array(); 2108 2109 if (count($decoded)) 2110 { 2111 foreach ($decoded as $myTableName => $tableFields) 2112 { 2113 $temp = array(); 2114 2115 if (is_array($tableFields)) 2116 { 2117 foreach($tableFields as $field => $def) 2118 { 2119 $temp[$field] = (object)$def; 2120 } 2121 $tableCache[$myTableName] = $temp; 2122 } 2123 elseif (is_object($tableFields) || is_bool($tableFields)) 2124 { 2125 $tableCache[$myTableName] = $tableFields; 2126 } 2127 } 2128 } 2129 2130 self::$tableFieldCache = $tableCache; 2131 } 2132 else 2133 { 2134 self::$tableFieldCache = array(); 2135 } 2136 } 2137 } 2138 2139 if (!$tableName) 2140 { 2141 $tableName = $this->_tbl; 2142 } 2143 2144 // Try to load again column specifications if the table is not loaded OR if it's loaded and 2145 // the previous call returned an error 2146 if (!array_key_exists($tableName, self::$tableFieldCache) || 2147 (isset(self::$tableFieldCache[$tableName]) && !self::$tableFieldCache[$tableName])) 2148 { 2149 // Lookup the fields for this table only once. 2150 $name = $tableName; 2151 2152 $prefix = $this->_db->getPrefix(); 2153 2154 if (substr($name, 0, 3) == '#__') 2155 { 2156 $checkName = $prefix . substr($name, 3); 2157 } 2158 else 2159 { 2160 $checkName = $name; 2161 } 2162 2163 if (!in_array($checkName, self::$tableCache)) 2164 { 2165 // The table doesn't exist. Return false. 2166 self::$tableFieldCache[$tableName] = false; 2167 } 2168 elseif (version_compare(JVERSION, '3.0', 'ge')) 2169 { 2170 $fields = $this->_db->getTableColumns($name, false); 2171 2172 if (empty($fields)) 2173 { 2174 $fields = false; 2175 } 2176 2177 self::$tableFieldCache[$tableName] = $fields; 2178 } 2179 else 2180 { 2181 $fields = $this->_db->getTableFields($name, false); 2182 2183 if (!isset($fields[$name])) 2184 { 2185 $fields = false; 2186 } 2187 2188 self::$tableFieldCache[$tableName] = $fields[$name]; 2189 } 2190 2191 // PostgreSQL date type compatibility 2192 if (($this->_db->name == 'postgresql') && (self::$tableFieldCache[$tableName] != false)) 2193 { 2194 foreach (self::$tableFieldCache[$tableName] as $field) 2195 { 2196 if (strtolower($field->type) == 'timestamp without time zone') 2197 { 2198 if (stristr($field->Default, '\'::timestamp without time zone')) 2199 { 2200 list ($date, $junk) = explode('::', $field->Default, 2); 2201 $field->Default = trim($date, "'"); 2202 } 2203 } 2204 } 2205 } 2206 2207 // Save the data for this table into the cache 2208 if ($useCache) 2209 { 2210 $cacheData = FOFPlatform::getInstance()->setCache('tablefields', json_encode(self::$tableFieldCache)); 2211 } 2212 } 2213 2214 return self::$tableFieldCache[$tableName]; 2215 } 2216 2217 public function getTableAlias() 2218 { 2219 return $this->_tableAlias; 2220 } 2221 2222 public function setTableAlias($string) 2223 { 2224 $string = preg_replace('#[^A-Z0-9_]#i', '', $string); 2225 $this->_tableAlias = $string; 2226 } 2227 2228 /** 2229 * Method to return the real name of a "special" column such as ordering, hits, published 2230 * etc etc. In this way you are free to follow your db naming convention and use the 2231 * built in Joomla functions. 2232 * 2233 * @param string $column Name of the "special" column (ie ordering, hits etc etc) 2234 * 2235 * @return string The string that identify the special 2236 */ 2237 public function getColumnAlias($column) 2238 { 2239 if (isset($this->_columnAlias[$column])) 2240 { 2241 $return = $this->_columnAlias[$column]; 2242 } 2243 else 2244 { 2245 $return = $column; 2246 } 2247 2248 $return = preg_replace('#[^A-Z0-9_]#i', '', $return); 2249 2250 return $return; 2251 } 2252 2253 /** 2254 * Method to register a column alias for a "special" column. 2255 * 2256 * @param string $column The "special" column (ie ordering) 2257 * @param string $columnAlias The real column name (ie foo_ordering) 2258 * 2259 * @return void 2260 */ 2261 public function setColumnAlias($column, $columnAlias) 2262 { 2263 $column = strtolower($column); 2264 2265 $column = preg_replace('#[^A-Z0-9_]#i', '', $column); 2266 $this->_columnAlias[$column] = $columnAlias; 2267 } 2268 2269 /** 2270 * Get a JOIN query, used to join other tables 2271 * 2272 * @param boolean $asReference Return an object reference instead of a copy 2273 * 2274 * @return FOFDatabaseQuery Query used to join other tables 2275 */ 2276 public function getQueryJoin($asReference = false) 2277 { 2278 if ($asReference) 2279 { 2280 return $this->_queryJoin; 2281 } 2282 else 2283 { 2284 if ($this->_queryJoin) 2285 { 2286 return clone $this->_queryJoin; 2287 } 2288 else 2289 { 2290 return null; 2291 } 2292 } 2293 } 2294 2295 /** 2296 * Sets the query with joins to other tables 2297 * 2298 * @param FOFDatabaseQuery $query The JOIN query to use 2299 * 2300 * @return void 2301 */ 2302 public function setQueryJoin($query) 2303 { 2304 $this->_queryJoin = $query; 2305 } 2306 2307 /** 2308 * Extracts the fields from the join query 2309 * 2310 * @return array Fields contained in the join query 2311 */ 2312 protected function getQueryJoinFields() 2313 { 2314 $query = $this->getQueryJoin(); 2315 2316 if (!$query) 2317 { 2318 return array(); 2319 } 2320 2321 $tables = array(); 2322 $j_tables = array(); 2323 $j_fields = array(); 2324 2325 // Get joined tables. Ignore FROM clause, since it should not be used (the starting point is the table "table") 2326 $joins = $query->join; 2327 2328 foreach ($joins as $join) 2329 { 2330 $tables = array_merge($tables, $join->getElements()); 2331 } 2332 2333 // Clean up table names 2334 foreach($tables as $table) 2335 { 2336 preg_match('#(.*)((\w)*(on|using))(.*)#i', $table, $matches); 2337 2338 if($matches && isset($matches[1])) 2339 { 2340 // I always want the first part, no matter what 2341 $parts = explode(' ', $matches[1]); 2342 $t_table = $parts[0]; 2343 2344 if($this->isQuoted($t_table)) 2345 { 2346 $t_table = substr($t_table, 1, strlen($t_table) - 2); 2347 } 2348 2349 if(!in_array($t_table, $j_tables)) 2350 { 2351 $j_tables[] = $t_table; 2352 } 2353 } 2354 } 2355 2356 // Do I have the current table inside the query join? Remove it (its fields are already ok) 2357 $find = array_search($this->getTableName(), $j_tables); 2358 if($find !== false) 2359 { 2360 unset($j_tables[$find]); 2361 } 2362 2363 // Get table fields 2364 $fields = array(); 2365 2366 foreach ($j_tables as $table) 2367 { 2368 $t_fields = $this->getTableFields($table); 2369 2370 if ($t_fields) 2371 { 2372 $fields = array_merge($fields, $t_fields); 2373 } 2374 } 2375 2376 // Remove any fields that aren't in the joined select 2377 $j_select = $query->select; 2378 2379 if ($j_select && $j_select->getElements()) 2380 { 2381 $j_fields = $this->normalizeSelectFields($j_select->getElements()); 2382 } 2383 2384 // I can intesect the keys 2385 $fields = array_intersect_key($fields, $j_fields); 2386 2387 // Now I walk again the array to change the key of columns that have an alias 2388 foreach ($j_fields as $column => $alias) 2389 { 2390 if ($column != $alias) 2391 { 2392 $fields[$alias] = $fields[$column]; 2393 unset($fields[$column]); 2394 } 2395 } 2396 2397 return $fields; 2398 } 2399 2400 /** 2401 * Normalizes the fields, returning an associative array with all the fields. 2402 * Ie array('foobar as foo, bar') becomes array('foobar' => 'foo', 'bar' => 'bar') 2403 * 2404 * @param array $fields Array with column fields 2405 * 2406 * @return array Normalized array 2407 */ 2408 protected function normalizeSelectFields($fields) 2409 { 2410 $db = FOFPlatform::getInstance()->getDbo(); 2411 $return = array(); 2412 2413 foreach ($fields as $field) 2414 { 2415 $t_fields = explode(',', $field); 2416 2417 foreach ($t_fields as $t_field) 2418 { 2419 // Is there any alias? 2420 $parts = preg_split('#\sas\s#i', $t_field); 2421 2422 // Do I have a table.column situation? Let's get the field name 2423 $tableField = explode('.', $parts[0]); 2424 2425 if(isset($tableField[1])) 2426 { 2427 $column = trim($tableField[1]); 2428 } 2429 else 2430 { 2431 $column = trim($tableField[0]); 2432 } 2433 2434 // Is this field quoted? If so, remove the quotes 2435 if($this->isQuoted($column)) 2436 { 2437 $column = substr($column, 1, strlen($column) - 2); 2438 } 2439 2440 if(isset($parts[1])) 2441 { 2442 $alias = trim($parts[1]); 2443 2444 // Is this field quoted? If so, remove the quotes 2445 if($this->isQuoted($alias)) 2446 { 2447 $alias = substr($alias, 1, strlen($alias) - 2); 2448 } 2449 } 2450 else 2451 { 2452 $alias = $column; 2453 } 2454 2455 $return[$column] = $alias; 2456 } 2457 } 2458 2459 return $return; 2460 } 2461 2462 /** 2463 * Is the field quoted? 2464 * 2465 * @param string $column Column field 2466 * 2467 * @return bool Is the field quoted? 2468 */ 2469 protected function isQuoted($column) 2470 { 2471 // Empty string, un-quoted by definition 2472 if(!$column) 2473 { 2474 return false; 2475 } 2476 2477 // I need some "magic". If the first char is not a letter, a number 2478 // an underscore or # (needed for table), then most likely the field is quoted 2479 preg_match_all('/^[a-z0-9_#]/i', $column, $matches); 2480 2481 if(!$matches[0]) 2482 { 2483 return true; 2484 } 2485 2486 return false; 2487 } 2488 2489 /** 2490 * The event which runs before binding data to the table 2491 * 2492 * NOTE TO 3RD PARTY DEVELOPERS: 2493 * 2494 * When you override the following methods in your child classes, 2495 * be sure to call parent::method *AFTER* your code, otherwise the 2496 * plugin events do NOT get triggered 2497 * 2498 * Example: 2499 * protected function onBeforeBind(){ 2500 * // Your code here 2501 * return parent::onBeforeBind() && $your_result; 2502 * } 2503 * 2504 * Do not do it the other way around, e.g. return $your_result && parent::onBeforeBind() 2505 * Due to PHP short-circuit boolean evaluation the parent::onBeforeBind() 2506 * will not be called if $your_result is false. 2507 * 2508 * @param object|array &$from The data to bind 2509 * 2510 * @return boolean True on success 2511 */ 2512 protected function onBeforeBind(&$from) 2513 { 2514 // Call the behaviors 2515 $result = $this->tableDispatcher->trigger('onBeforeBind', array(&$this, &$from)); 2516 2517 if (in_array(false, $result, true)) 2518 { 2519 // Behavior failed, return false 2520 return false; 2521 } 2522 2523 if ($this->_trigger_events) 2524 { 2525 $name = FOFInflector::pluralize($this->getKeyName()); 2526 2527 $result = FOFPlatform::getInstance()->runPlugins('onBeforeBind' . ucfirst($name), array(&$this, &$from)); 2528 2529 if (in_array(false, $result, true)) 2530 { 2531 return false; 2532 } 2533 else 2534 { 2535 return true; 2536 } 2537 } 2538 2539 return true; 2540 } 2541 2542 /** 2543 * The event which runs after loading a record from the database 2544 * 2545 * @param boolean &$result Did the load succeeded? 2546 * 2547 * @return void 2548 */ 2549 protected function onAfterLoad(&$result) 2550 { 2551 // Call the behaviors 2552 $eventResult = $this->tableDispatcher->trigger('onAfterLoad', array(&$this, &$result)); 2553 2554 if (in_array(false, $eventResult, true)) 2555 { 2556 // Behavior failed, return false 2557 $result = false; 2558 return false; 2559 } 2560 2561 if ($this->_trigger_events) 2562 { 2563 $name = FOFInflector::pluralize($this->getKeyName()); 2564 2565 FOFPlatform::getInstance()->runPlugins('onAfterLoad' . ucfirst($name), array(&$this, &$result)); 2566 } 2567 } 2568 2569 /** 2570 * The event which runs before storing (saving) data to the database 2571 * 2572 * @param boolean $updateNulls Should nulls be saved as nulls (true) or just skipped over (false)? 2573 * 2574 * @return boolean True to allow saving 2575 */ 2576 protected function onBeforeStore($updateNulls) 2577 { 2578 // Do we have a "Created" set of fields? 2579 $created_on = $this->getColumnAlias('created_on'); 2580 $created_by = $this->getColumnAlias('created_by'); 2581 $modified_on = $this->getColumnAlias('modified_on'); 2582 $modified_by = $this->getColumnAlias('modified_by'); 2583 $locked_on = $this->getColumnAlias('locked_on'); 2584 $locked_by = $this->getColumnAlias('locked_by'); 2585 $title = $this->getColumnAlias('title'); 2586 $slug = $this->getColumnAlias('slug'); 2587 2588 $hasCreatedOn = in_array($created_on, $this->getKnownFields()); 2589 $hasCreatedBy = in_array($created_by, $this->getKnownFields()); 2590 2591 if ($hasCreatedOn && $hasCreatedBy) 2592 { 2593 $hasModifiedOn = in_array($modified_on, $this->getKnownFields()); 2594 $hasModifiedBy = in_array($modified_by, $this->getKnownFields()); 2595 2596 $nullDate = $this->_db->getNullDate(); 2597 2598 if (empty($this->$created_by) || ($this->$created_on == $nullDate) || empty($this->$created_on)) 2599 { 2600 $uid = FOFPlatform::getInstance()->getUser()->id; 2601 2602 if ($uid) 2603 { 2604 $this->$created_by = FOFPlatform::getInstance()->getUser()->id; 2605 } 2606 2607 $date = FOFPlatform::getInstance()->getDate('now', null, false); 2608 2609 $this->$created_on = method_exists($date, 'toSql') ? $date->toSql() : $date->toMySQL(); 2610 } 2611 elseif ($hasModifiedOn && $hasModifiedBy) 2612 { 2613 $uid = FOFPlatform::getInstance()->getUser()->id; 2614 2615 if ($uid) 2616 { 2617 $this->$modified_by = FOFPlatform::getInstance()->getUser()->id; 2618 } 2619 2620 $date = FOFPlatform::getInstance()->getDate('now', null, false); 2621 2622 $this->$modified_on = method_exists($date, 'toSql') ? $date->toSql() : $date->toMySQL(); 2623 } 2624 } 2625 2626 // Do we have a set of title and slug fields? 2627 $hasTitle = in_array($title, $this->getKnownFields()); 2628 $hasSlug = in_array($slug, $this->getKnownFields()); 2629 2630 if ($hasTitle && $hasSlug) 2631 { 2632 if (empty($this->$slug)) 2633 { 2634 // Create a slug from the title 2635 $this->$slug = FOFStringUtils::toSlug($this->$title); 2636 } 2637 else 2638 { 2639 // Filter the slug for invalid characters 2640 $this->$slug = FOFStringUtils::toSlug($this->$slug); 2641 } 2642 2643 // Make sure we don't have a duplicate slug on this table 2644 $db = $this->getDbo(); 2645 $query = $db->getQuery(true) 2646 ->select($db->qn($slug)) 2647 ->from($this->_tbl) 2648 ->where($db->qn($slug) . ' = ' . $db->q($this->$slug)) 2649 ->where('NOT ' . $db->qn($this->_tbl_key) . ' = ' . $db->q($this->{$this->_tbl_key})); 2650 $db->setQuery($query); 2651 $existingItems = $db->loadAssocList(); 2652 2653 $count = 0; 2654 $newSlug = $this->$slug; 2655 2656 while (!empty($existingItems)) 2657 { 2658 $count++; 2659 $newSlug = $this->$slug . '-' . $count; 2660 $query = $db->getQuery(true) 2661 ->select($db->qn($slug)) 2662 ->from($this->_tbl) 2663 ->where($db->qn($slug) . ' = ' . $db->q($newSlug)) 2664 ->where('NOT '. $db->qn($this->_tbl_key) . ' = ' . $db->q($this->{$this->_tbl_key})); 2665 $db->setQuery($query); 2666 $existingItems = $db->loadAssocList(); 2667 } 2668 2669 $this->$slug = $newSlug; 2670 } 2671 2672 // Call the behaviors 2673 $result = $this->tableDispatcher->trigger('onBeforeStore', array(&$this, $updateNulls)); 2674 2675 if (in_array(false, $result, true)) 2676 { 2677 // Behavior failed, return false 2678 return false; 2679 } 2680 2681 // Execute onBeforeStore<tablename> events in loaded plugins 2682 if ($this->_trigger_events) 2683 { 2684 $name = FOFInflector::pluralize($this->getKeyName()); 2685 $result = FOFPlatform::getInstance()->runPlugins('onBeforeStore' . ucfirst($name), array(&$this, $updateNulls)); 2686 2687 if (in_array(false, $result, true)) 2688 { 2689 return false; 2690 } 2691 else 2692 { 2693 return true; 2694 } 2695 } 2696 2697 return true; 2698 } 2699 2700 /** 2701 * The event which runs after binding data to the class 2702 * 2703 * @param object|array &$src The data to bind 2704 * 2705 * @return boolean True to allow binding without an error 2706 */ 2707 protected function onAfterBind(&$src) 2708 { 2709 // Call the behaviors 2710 $options = array( 2711 'component' => $this->input->get('option'), 2712 'view' => $this->input->get('view'), 2713 'table_prefix' => $this->_tablePrefix 2714 ); 2715 2716 $result = $this->tableDispatcher->trigger('onAfterBind', array(&$this, &$src, $options)); 2717 2718 if (in_array(false, $result, true)) 2719 { 2720 // Behavior failed, return false 2721 return false; 2722 } 2723 2724 if ($this->_trigger_events) 2725 { 2726 $name = FOFInflector::pluralize($this->getKeyName()); 2727 2728 $result = FOFPlatform::getInstance()->runPlugins('onAfterBind' . ucfirst($name), array(&$this, &$src)); 2729 2730 if (in_array(false, $result, true)) 2731 { 2732 return false; 2733 } 2734 else 2735 { 2736 return true; 2737 } 2738 } 2739 2740 return true; 2741 } 2742 2743 /** 2744 * The event which runs after storing (saving) data to the database 2745 * 2746 * @return boolean True to allow saving without an error 2747 */ 2748 protected function onAfterStore() 2749 { 2750 // Call the behaviors 2751 $result = $this->tableDispatcher->trigger('onAfterStore', array(&$this)); 2752 2753 if (in_array(false, $result, true)) 2754 { 2755 // Behavior failed, return false 2756 return false; 2757 } 2758 2759 if ($this->_trigger_events) 2760 { 2761 $name = FOFInflector::pluralize($this->getKeyName()); 2762 2763 $result = FOFPlatform::getInstance()->runPlugins('onAfterStore' . ucfirst($name), array(&$this)); 2764 2765 if (in_array(false, $result, true)) 2766 { 2767 return false; 2768 } 2769 else 2770 { 2771 return true; 2772 } 2773 } 2774 2775 return true; 2776 } 2777 2778 /** 2779 * The event which runs before moving a record 2780 * 2781 * @param boolean $updateNulls Should nulls be saved as nulls (true) or just skipped over (false)? 2782 * 2783 * @return boolean True to allow moving 2784 */ 2785 protected function onBeforeMove($updateNulls) 2786 { 2787 // Call the behaviors 2788 $result = $this->tableDispatcher->trigger('onBeforeMove', array(&$this, $updateNulls)); 2789 2790 if (in_array(false, $result, true)) 2791 { 2792 // Behavior failed, return false 2793 return false; 2794 } 2795 2796 if ($this->_trigger_events) 2797 { 2798 $name = FOFInflector::pluralize($this->getKeyName()); 2799 2800 $result = FOFPlatform::getInstance()->runPlugins('onBeforeMove' . ucfirst($name), array(&$this, $updateNulls)); 2801 2802 if (in_array(false, $result, true)) 2803 { 2804 return false; 2805 } 2806 else 2807 { 2808 return true; 2809 } 2810 } 2811 2812 return true; 2813 } 2814 2815 /** 2816 * The event which runs after moving a record 2817 * 2818 * @return boolean True to allow moving without an error 2819 */ 2820 protected function onAfterMove() 2821 { 2822 // Call the behaviors 2823 $result = $this->tableDispatcher->trigger('onAfterMove', array(&$this)); 2824 2825 if (in_array(false, $result, true)) 2826 { 2827 // Behavior failed, return false 2828 return false; 2829 } 2830 2831 if ($this->_trigger_events) 2832 { 2833 $name = FOFInflector::pluralize($this->getKeyName()); 2834 2835 $result = FOFPlatform::getInstance()->runPlugins('onAfterMove' . ucfirst($name), array(&$this)); 2836 2837 if (in_array(false, $result, true)) 2838 { 2839 return false; 2840 } 2841 else 2842 { 2843 return true; 2844 } 2845 } 2846 2847 return true; 2848 } 2849 2850 /** 2851 * The event which runs before reordering a table 2852 * 2853 * @param string $where The WHERE clause of the SQL query to run on reordering (record filter) 2854 * 2855 * @return boolean True to allow reordering 2856 */ 2857 protected function onBeforeReorder($where = '') 2858 { 2859 // Call the behaviors 2860 $result = $this->tableDispatcher->trigger('onBeforeReorder', array(&$this, $where)); 2861 2862 if (in_array(false, $result, true)) 2863 { 2864 // Behavior failed, return false 2865 return false; 2866 } 2867 2868 if ($this->_trigger_events) 2869 { 2870 $name = FOFInflector::pluralize($this->getKeyName()); 2871 2872 $result = FOFPlatform::getInstance()->runPlugins('onBeforeReorder' . ucfirst($name), array(&$this, $where)); 2873 2874 if (in_array(false, $result, true)) 2875 { 2876 return false; 2877 } 2878 else 2879 { 2880 return true; 2881 } 2882 } 2883 2884 return true; 2885 } 2886 2887 /** 2888 * The event which runs after reordering a table 2889 * 2890 * @return boolean True to allow the reordering to complete without an error 2891 */ 2892 protected function onAfterReorder() 2893 { 2894 // Call the behaviors 2895 $result = $this->tableDispatcher->trigger('onAfterReorder', array(&$this)); 2896 2897 if (in_array(false, $result, true)) 2898 { 2899 // Behavior failed, return false 2900 return false; 2901 } 2902 2903 if ($this->_trigger_events) 2904 { 2905 $name = FOFInflector::pluralize($this->getKeyName()); 2906 2907 $result = FOFPlatform::getInstance()->runPlugins('onAfterReorder' . ucfirst($name), array(&$this)); 2908 2909 if (in_array(false, $result, true)) 2910 { 2911 return false; 2912 } 2913 else 2914 { 2915 return true; 2916 } 2917 } 2918 2919 return true; 2920 } 2921 2922 /** 2923 * The event which runs before deleting a record 2924 * 2925 * @param integer $oid The PK value of the record to delete 2926 * 2927 * @return boolean True to allow the deletion 2928 */ 2929 protected function onBeforeDelete($oid) 2930 { 2931 // Call the behaviors 2932 $result = $this->tableDispatcher->trigger('onBeforeDelete', array(&$this, $oid)); 2933 2934 if (in_array(false, $result, true)) 2935 { 2936 // Behavior failed, return false 2937 return false; 2938 } 2939 2940 if ($this->_trigger_events) 2941 { 2942 $name = FOFInflector::pluralize($this->getKeyName()); 2943 2944 $result = FOFPlatform::getInstance()->runPlugins('onBeforeDelete' . ucfirst($name), array(&$this, $oid)); 2945 2946 if (in_array(false, $result, true)) 2947 { 2948 return false; 2949 } 2950 else 2951 { 2952 return true; 2953 } 2954 } 2955 2956 return true; 2957 } 2958 2959 /** 2960 * The event which runs after deleting a record 2961 * 2962 * @param integer $oid The PK value of the record which was deleted 2963 * 2964 * @return boolean True to allow the deletion without errors 2965 */ 2966 protected function onAfterDelete($oid) 2967 { 2968 // Call the behaviors 2969 $result = $this->tableDispatcher->trigger('onAfterDelete', array(&$this, $oid)); 2970 2971 if (in_array(false, $result, true)) 2972 { 2973 // Behavior failed, return false 2974 return false; 2975 } 2976 2977 if ($this->_trigger_events) 2978 { 2979 $name = FOFInflector::pluralize($this->getKeyName()); 2980 2981 $result = FOFPlatform::getInstance()->runPlugins('onAfterDelete' . ucfirst($name), array(&$this, $oid)); 2982 2983 if (in_array(false, $result, true)) 2984 { 2985 return false; 2986 } 2987 else 2988 { 2989 return true; 2990 } 2991 } 2992 2993 return true; 2994 } 2995 2996 /** 2997 * The event which runs before hitting a record 2998 * 2999 * @param integer $oid The PK value of the record to hit 3000 * @param boolean $log Should we log the hit? 3001 * 3002 * @return boolean True to allow the hit 3003 */ 3004 protected function onBeforeHit($oid, $log) 3005 { 3006 // Call the behaviors 3007 $result = $this->tableDispatcher->trigger('onBeforeHit', array(&$this, $oid, $log)); 3008 3009 if (in_array(false, $result, true)) 3010 { 3011 // Behavior failed, return false 3012 return false; 3013 } 3014 3015 if ($this->_trigger_events) 3016 { 3017 $name = FOFInflector::pluralize($this->getKeyName()); 3018 3019 $result = FOFPlatform::getInstance()->runPlugins('onBeforeHit' . ucfirst($name), array(&$this, $oid, $log)); 3020 3021 if (in_array(false, $result, true)) 3022 { 3023 return false; 3024 } 3025 else 3026 { 3027 return true; 3028 } 3029 } 3030 3031 return true; 3032 } 3033 3034 /** 3035 * The event which runs after hitting a record 3036 * 3037 * @param integer $oid The PK value of the record which was hit 3038 * 3039 * @return boolean True to allow the hitting without errors 3040 */ 3041 protected function onAfterHit($oid) 3042 { 3043 // Call the behaviors 3044 $result = $this->tableDispatcher->trigger('onAfterHit', array(&$this, $oid)); 3045 3046 if (in_array(false, $result, true)) 3047 { 3048 // Behavior failed, return false 3049 return false; 3050 } 3051 3052 if ($this->_trigger_events) 3053 { 3054 $name = FOFInflector::pluralize($this->getKeyName()); 3055 3056 $result = FOFPlatform::getInstance()->runPlugins('onAfterHit' . ucfirst($name), array(&$this, $oid)); 3057 3058 if (in_array(false, $result, true)) 3059 { 3060 return false; 3061 } 3062 else 3063 { 3064 return true; 3065 } 3066 } 3067 3068 return true; 3069 } 3070 3071 /** 3072 * The even which runs before copying a record 3073 * 3074 * @param integer $oid The PK value of the record being copied 3075 * 3076 * @return boolean True to allow the copy to take place 3077 */ 3078 protected function onBeforeCopy($oid) 3079 { 3080 // Call the behaviors 3081 $result = $this->tableDispatcher->trigger('onBeforeCopy', array(&$this, $oid)); 3082 3083 if (in_array(false, $result, true)) 3084 { 3085 // Behavior failed, return false 3086 return false; 3087 } 3088 3089 if ($this->_trigger_events) 3090 { 3091 $name = FOFInflector::pluralize($this->getKeyName()); 3092 3093 $result = FOFPlatform::getInstance()->runPlugins('onBeforeCopy' . ucfirst($name), array(&$this, $oid)); 3094 3095 if (in_array(false, $result, true)) 3096 { 3097 return false; 3098 } 3099 else 3100 { 3101 return true; 3102 } 3103 } 3104 3105 return true; 3106 } 3107 3108 /** 3109 * The even which runs after copying a record 3110 * 3111 * @param integer $oid The PK value of the record which was copied (not the new one) 3112 * 3113 * @return boolean True to allow the copy without errors 3114 */ 3115 protected function onAfterCopy($oid) 3116 { 3117 // Call the behaviors 3118 $result = $this->tableDispatcher->trigger('onAfterCopy', array(&$this, $oid)); 3119 3120 if (in_array(false, $result, true)) 3121 { 3122 // Behavior failed, return false 3123 return false; 3124 } 3125 3126 if ($this->_trigger_events) 3127 { 3128 $name = FOFInflector::pluralize($this->getKeyName()); 3129 3130 $result = FOFPlatform::getInstance()->runPlugins('onAfterCopy' . ucfirst($name), array(&$this, $oid)); 3131 3132 if (in_array(false, $result, true)) 3133 { 3134 return false; 3135 } 3136 else 3137 { 3138 return true; 3139 } 3140 } 3141 3142 return true; 3143 } 3144 3145 /** 3146 * The event which runs before a record is (un)published 3147 * 3148 * @param integer|array &$cid The PK IDs of the records being (un)published 3149 * @param integer $publish 1 to publish, 0 to unpublish 3150 * 3151 * @return boolean True to allow the (un)publish to proceed 3152 */ 3153 protected function onBeforePublish(&$cid, $publish) 3154 { 3155 // Call the behaviors 3156 $result = $this->tableDispatcher->trigger('onBeforePublish', array(&$this, &$cid, $publish)); 3157 3158 if (in_array(false, $result, true)) 3159 { 3160 // Behavior failed, return false 3161 return false; 3162 } 3163 3164 if ($this->_trigger_events) 3165 { 3166 $name = FOFInflector::pluralize($this->getKeyName()); 3167 3168 $result = FOFPlatform::getInstance()->runPlugins('onBeforePublish' . ucfirst($name), array(&$this, &$cid, $publish)); 3169 3170 if (in_array(false, $result, true)) 3171 { 3172 return false; 3173 } 3174 else 3175 { 3176 return true; 3177 } 3178 } 3179 3180 return true; 3181 } 3182 3183 /** 3184 * The event which runs after the object is reset to its default values. 3185 * 3186 * @return boolean True to allow the reset to complete without errors 3187 */ 3188 protected function onAfterReset() 3189 { 3190 // Call the behaviors 3191 $result = $this->tableDispatcher->trigger('onAfterReset', array(&$this)); 3192 3193 if (in_array(false, $result, true)) 3194 { 3195 // Behavior failed, return false 3196 return false; 3197 } 3198 3199 if ($this->_trigger_events) 3200 { 3201 $name = FOFInflector::pluralize($this->getKeyName()); 3202 3203 $result = FOFPlatform::getInstance()->runPlugins('onAfterReset' . ucfirst($name), array(&$this)); 3204 3205 if (in_array(false, $result, true)) 3206 { 3207 return false; 3208 } 3209 else 3210 { 3211 return true; 3212 } 3213 } 3214 3215 return true; 3216 } 3217 3218 /** 3219 * The even which runs before the object is reset to its default values. 3220 * 3221 * @return boolean True to allow the reset to complete 3222 */ 3223 protected function onBeforeReset() 3224 { 3225 // Call the behaviors 3226 $result = $this->tableDispatcher->trigger('onBeforeReset', array(&$this)); 3227 3228 if (in_array(false, $result, true)) 3229 { 3230 // Behavior failed, return false 3231 return false; 3232 } 3233 3234 if ($this->_trigger_events) 3235 { 3236 $name = FOFInflector::pluralize($this->getKeyName()); 3237 3238 $result = FOFPlatform::getInstance()->runPlugins('onBeforeReset' . ucfirst($name), array(&$this)); 3239 3240 if (in_array(false, $result, true)) 3241 { 3242 return false; 3243 } 3244 else 3245 { 3246 return true; 3247 } 3248 } 3249 3250 return true; 3251 } 3252 3253 /** 3254 * Replace the input object of this table with the provided FOFInput object 3255 * 3256 * @param FOFInput $input The new input object 3257 * 3258 * @return void 3259 */ 3260 public function setInput(FOFInput $input) 3261 { 3262 $this->input = $input; 3263 } 3264 3265 /** 3266 * Get the columns from database table. 3267 * 3268 * @return mixed An array of the field names, or false if an error occurs. 3269 * 3270 * @deprecated 2.1 3271 */ 3272 public function getFields() 3273 { 3274 return $this->getTableFields(); 3275 } 3276 3277 /** 3278 * Add a filesystem path where FOFTable should search for table class files. 3279 * You may either pass a string or an array of paths. 3280 * 3281 * @param mixed $path A filesystem path or array of filesystem paths to add. 3282 * 3283 * @return array An array of filesystem paths to find FOFTable classes in. 3284 */ 3285 public static function addIncludePath($path = null) 3286 { 3287 // If the internal paths have not been initialised, do so with the base table path. 3288 if (empty(self::$_includePaths)) 3289 { 3290 self::$_includePaths = array(__DIR__); 3291 } 3292 3293 // Convert the passed path(s) to add to an array. 3294 settype($path, 'array'); 3295 3296 // If we have new paths to add, do so. 3297 if (!empty($path) && !in_array($path, self::$_includePaths)) 3298 { 3299 // Check and add each individual new path. 3300 foreach ($path as $dir) 3301 { 3302 // Sanitize path. 3303 $dir = trim($dir); 3304 3305 // Add to the front of the list so that custom paths are searched first. 3306 array_unshift(self::$_includePaths, $dir); 3307 } 3308 } 3309 3310 return self::$_includePaths; 3311 } 3312 3313 /** 3314 * Loads the asset table related to this table. 3315 * This will help tests, too, since we can mock this function. 3316 * 3317 * @return bool|JTableAsset False on failure, otherwise JTableAsset 3318 */ 3319 protected function getAsset() 3320 { 3321 $name = $this->_getAssetName(); 3322 3323 // Do NOT touch JTable here -- we are loading the core asset table which is a JTable, not a FOFTable 3324 $asset = JTable::getInstance('Asset'); 3325 3326 if (!$asset->loadByName($name)) 3327 { 3328 return false; 3329 } 3330 3331 return $asset; 3332 } 3333 3334 /** 3335 * Method to compute the default name of the asset. 3336 * The default name is in the form table_name.id 3337 * where id is the value of the primary key of the table. 3338 * 3339 * @throws UnexpectedValueException 3340 * 3341 * @return string 3342 */ 3343 public function getAssetName() 3344 { 3345 $k = $this->_tbl_key; 3346 3347 // If there is no assetKey defined, stop here, or we'll get a wrong name 3348 if(!$this->_assetKey || !$this->$k) 3349 { 3350 throw new UnexpectedValueException('Table must have an asset key defined and a value for the table id in order to track assets'); 3351 } 3352 3353 return $this->_assetKey . '.' . (int) $this->$k; 3354 } 3355 3356 /** 3357 * Method to compute the default name of the asset. 3358 * The default name is in the form table_name.id 3359 * where id is the value of the primary key of the table. 3360 * 3361 * @throws UnexpectedValueException 3362 * 3363 * @return string 3364 */ 3365 public function getAssetKey() 3366 { 3367 return $this->_assetKey; 3368 } 3369 3370 /** 3371 * Method to return the title to use for the asset table. In 3372 * tracking the assets a title is kept for each asset so that there is some 3373 * context available in a unified access manager. Usually this would just 3374 * return $this->title or $this->name or whatever is being used for the 3375 * primary name of the row. If this method is not overridden, the asset name is used. 3376 * 3377 * @return string The string to use as the title in the asset table. 3378 */ 3379 public function getAssetTitle() 3380 { 3381 return $this->getAssetName(); 3382 } 3383 3384 /** 3385 * Method to get the parent asset under which to register this one. 3386 * By default, all assets are registered to the ROOT node with ID, 3387 * which will default to 1 if none exists. 3388 * The extended class can define a table and id to lookup. If the 3389 * asset does not exist it will be created. 3390 * 3391 * @param FOFTable $table A FOFTable object for the asset parent. 3392 * @param integer $id Id to look up 3393 * 3394 * @return integer 3395 */ 3396 public function getAssetParentId($table = null, $id = null) 3397 { 3398 // For simple cases, parent to the asset root. 3399 $assets = JTable::getInstance('Asset', 'JTable', array('dbo' => $this->getDbo())); 3400 $rootId = $assets->getRootId(); 3401 3402 if (!empty($rootId)) 3403 { 3404 return $rootId; 3405 } 3406 3407 return 1; 3408 } 3409 3410 /** 3411 * This method sets the asset key for the items of this table. Obviously, it 3412 * is only meant to be used when you have a table with an asset field. 3413 * 3414 * @param string $assetKey The name of the asset key to use 3415 * 3416 * @return void 3417 */ 3418 public function setAssetKey($assetKey) 3419 { 3420 $this->_assetKey = $assetKey; 3421 } 3422 3423 /** 3424 * Method to get the database table name for the class. 3425 * 3426 * @return string The name of the database table being modeled. 3427 */ 3428 public function getTableName() 3429 { 3430 return $this->_tbl; 3431 } 3432 3433 /** 3434 * Method to get the primary key field name for the table. 3435 * 3436 * @return string The name of the primary key for the table. 3437 */ 3438 public function getKeyName() 3439 { 3440 return $this->_tbl_key; 3441 } 3442 3443 /** 3444 * Returns the identity value of this record 3445 * 3446 * @return mixed 3447 */ 3448 public function getId() 3449 { 3450 $key = $this->getKeyName(); 3451 3452 return $this->$key; 3453 } 3454 3455 /** 3456 * Method to get the FOFDatabaseDriver object. 3457 * 3458 * @return FOFDatabaseDriver The internal database driver object. 3459 */ 3460 public function getDbo() 3461 { 3462 return $this->_db; 3463 } 3464 3465 /** 3466 * Method to set the FOFDatabaseDriver object. 3467 * 3468 * @param FOFDatabaseDriver $db A FOFDatabaseDriver object to be used by the table object. 3469 * 3470 * @return boolean True on success. 3471 */ 3472 public function setDBO($db) 3473 { 3474 $this->_db = $db; 3475 3476 return true; 3477 } 3478 3479 /** 3480 * Method to set rules for the record. 3481 * 3482 * @param mixed $input A JAccessRules object, JSON string, or array. 3483 * 3484 * @return void 3485 */ 3486 public function setRules($input) 3487 { 3488 if ($input instanceof JAccessRules) 3489 { 3490 $this->_rules = $input; 3491 } 3492 else 3493 { 3494 $this->_rules = new JAccessRules($input); 3495 } 3496 } 3497 3498 /** 3499 * Method to get the rules for the record. 3500 * 3501 * @return JAccessRules object 3502 */ 3503 public function getRules() 3504 { 3505 return $this->_rules; 3506 } 3507 3508 /** 3509 * Method to check if the record is treated as an ACL asset 3510 * 3511 * @return boolean [description] 3512 */ 3513 public function isAssetsTracked() 3514 { 3515 return $this->_trackAssets; 3516 } 3517 3518 /** 3519 * Method to manually set this record as ACL asset or not. 3520 * We have to do this since the automatic check is made in the constructor, but here we can't set any alias. 3521 * So, even if you have an alias for `asset_id`, it wouldn't be reconized and assets won't be tracked. 3522 * 3523 * @param $state 3524 */ 3525 public function setAssetsTracked($state) 3526 { 3527 $state = (bool) $state; 3528 3529 if($state) 3530 { 3531 JLoader::import('joomla.access.rules'); 3532 } 3533 3534 $this->_trackAssets = $state; 3535 } 3536 3537 /** 3538 * Method to provide a shortcut to binding, checking and storing a FOFTable 3539 * instance to the database table. The method will check a row in once the 3540 * data has been stored and if an ordering filter is present will attempt to 3541 * reorder the table rows based on the filter. The ordering filter is an instance 3542 * property name. The rows that will be reordered are those whose value matches 3543 * the FOFTable instance for the property specified. 3544 * 3545 * @param mixed $src An associative array or object to bind to the FOFTable instance. 3546 * @param string $orderingFilter Filter for the order updating 3547 * @param mixed $ignore An optional array or space separated list of properties 3548 * to ignore while binding. 3549 * 3550 * @return boolean True on success. 3551 */ 3552 public function save($src, $orderingFilter = '', $ignore = '') 3553 { 3554 // Attempt to bind the source to the instance. 3555 if (!$this->bind($src, $ignore)) 3556 { 3557 return false; 3558 } 3559 3560 // Run any sanity checks on the instance and verify that it is ready for storage. 3561 if (!$this->check()) 3562 { 3563 return false; 3564 } 3565 3566 // Attempt to store the properties to the database table. 3567 if (!$this->store()) 3568 { 3569 return false; 3570 } 3571 3572 // Attempt to check the row in, just in case it was checked out. 3573 if (!$this->checkin()) 3574 { 3575 return false; 3576 } 3577 3578 // If an ordering filter is set, attempt reorder the rows in the table based on the filter and value. 3579 if ($orderingFilter) 3580 { 3581 $filterValue = $this->$orderingFilter; 3582 $this->reorder($orderingFilter ? $this->_db->qn($orderingFilter) . ' = ' . $this->_db->q($filterValue) : ''); 3583 } 3584 3585 // Set the error to empty and return true. 3586 $this->setError(''); 3587 3588 return true; 3589 } 3590 3591 /** 3592 * Method to get the next ordering value for a group of rows defined by an SQL WHERE clause. 3593 * This is useful for placing a new item last in a group of items in the table. 3594 * 3595 * @param string $where WHERE clause to use for selecting the MAX(ordering) for the table. 3596 * 3597 * @return mixed Boolean false an failure or the next ordering value as an integer. 3598 */ 3599 public function getNextOrder($where = '') 3600 { 3601 // If there is no ordering field set an error and return false. 3602 $ordering = $this->getColumnAlias('ordering'); 3603 if (!in_array($ordering, $this->getKnownFields())) 3604 { 3605 throw new UnexpectedValueException(sprintf('%s does not support ordering.', get_class($this))); 3606 } 3607 3608 // Get the largest ordering value for a given where clause. 3609 $query = $this->_db->getQuery(true); 3610 $query->select('MAX('.$this->_db->qn($ordering).')'); 3611 $query->from($this->_tbl); 3612 3613 if ($where) 3614 { 3615 $query->where($where); 3616 } 3617 3618 $this->_db->setQuery($query); 3619 $max = (int) $this->_db->loadResult(); 3620 3621 // Return the largest ordering value + 1. 3622 return ($max + 1); 3623 } 3624 3625 /** 3626 * Method to lock the database table for writing. 3627 * 3628 * @return boolean True on success. 3629 * 3630 * @throws RuntimeException 3631 */ 3632 protected function _lock() 3633 { 3634 $this->_db->lockTable($this->_tbl); 3635 $this->_locked = true; 3636 3637 return true; 3638 } 3639 3640 /** 3641 * Method to unlock the database table for writing. 3642 * 3643 * @return boolean True on success. 3644 */ 3645 protected function _unlock() 3646 { 3647 $this->_db->unlockTables(); 3648 $this->_locked = false; 3649 3650 return true; 3651 } 3652 3653 public function setConfig(array $config) 3654 { 3655 $this->config = $config; 3656 } 3657 3658 /** 3659 * Get the content type for ucm 3660 * 3661 * @return string The content type alias 3662 */ 3663 public function getContentType() 3664 { 3665 if ($this->contentType) 3666 { 3667 return $this->contentType; 3668 } 3669 3670 /** 3671 * When tags was first introduced contentType variable didn't exist - so we guess one 3672 * This will fail if content history behvaiour is enabled. This code is deprecated 3673 * and will be removed in FOF 3.0 in favour of the content type class variable 3674 */ 3675 $component = $this->input->get('option'); 3676 3677 $view = FOFInflector::singularize($this->input->get('view')); 3678 $alias = $component . '.' . $view; 3679 3680 return $alias; 3681 } 3682 3683 /** 3684 * Returns the table relations object of the current table, lazy-loading it if necessary 3685 * 3686 * @return FOFTableRelations 3687 */ 3688 public function getRelations() 3689 { 3690 if (is_null($this->_relations)) 3691 { 3692 $this->_relations = new FOFTableRelations($this); 3693 } 3694 3695 return $this->_relations; 3696 } 3697 3698 /** 3699 * Gets a reference to the configuration parameters provider for this table 3700 * 3701 * @return FOFConfigProvider 3702 */ 3703 public function getConfigProvider() 3704 { 3705 return $this->configProvider; 3706 } 3707 3708 /** 3709 * Returns the configuration parameters provider's key for this table 3710 * 3711 * @return string 3712 */ 3713 public function getConfigProviderKey() 3714 { 3715 return $this->_configProviderKey; 3716 } 3717 3718 /** 3719 * Check if a UCM content type exists for this resource, and 3720 * create it if it does not 3721 * 3722 * @param string $alias The content type alias (optional) 3723 * 3724 * @return null 3725 */ 3726 public function checkContentType($alias = null) 3727 { 3728 $contentType = new JTableContenttype($this->getDbo()); 3729 3730 if (!$alias) 3731 { 3732 $alias = $this->getContentType(); 3733 } 3734 3735 $aliasParts = explode('.', $alias); 3736 3737 // Fetch the extension name 3738 $component = $aliasParts[0]; 3739 $component = JComponentHelper::getComponent($component); 3740 3741 // Fetch the name using the menu item 3742 $query = $this->getDbo()->getQuery(true); 3743 $query->select('title')->from('#__menu')->where('component_id = ' . (int) $component->id); 3744 $this->getDbo()->setQuery($query); 3745 $component_name = JText::_($this->getDbo()->loadResult()); 3746 3747 $name = $component_name . ' ' . ucfirst($aliasParts[1]); 3748 3749 // Create a new content type for our resource 3750 if (!$contentType->load(array('type_alias' => $alias))) 3751 { 3752 $contentType->type_title = $name; 3753 $contentType->type_alias = $alias; 3754 $contentType->table = json_encode( 3755 array( 3756 'special' => array( 3757 'dbtable' => $this->getTableName(), 3758 'key' => $this->getKeyName(), 3759 'type' => $name, 3760 'prefix' => $this->_tablePrefix, 3761 'class' => 'FOFTable', 3762 'config' => 'array()' 3763 ), 3764 'common' => array( 3765 'dbtable' => '#__ucm_content', 3766 'key' => 'ucm_id', 3767 'type' => 'CoreContent', 3768 'prefix' => 'JTable', 3769 'config' => 'array()' 3770 ) 3771 ) 3772 ); 3773 3774 $contentType->field_mappings = json_encode( 3775 array( 3776 'common' => array( 3777 0 => array( 3778 "core_content_item_id" => $this->getKeyName(), 3779 "core_title" => $this->getUcmCoreAlias('title'), 3780 "core_state" => $this->getUcmCoreAlias('enabled'), 3781 "core_alias" => $this->getUcmCoreAlias('alias'), 3782 "core_created_time" => $this->getUcmCoreAlias('created_on'), 3783 "core_modified_time" => $this->getUcmCoreAlias('created_by'), 3784 "core_body" => $this->getUcmCoreAlias('body'), 3785 "core_hits" => $this->getUcmCoreAlias('hits'), 3786 "core_publish_up" => $this->getUcmCoreAlias('publish_up'), 3787 "core_publish_down" => $this->getUcmCoreAlias('publish_down'), 3788 "core_access" => $this->getUcmCoreAlias('access'), 3789 "core_params" => $this->getUcmCoreAlias('params'), 3790 "core_featured" => $this->getUcmCoreAlias('featured'), 3791 "core_metadata" => $this->getUcmCoreAlias('metadata'), 3792 "core_language" => $this->getUcmCoreAlias('language'), 3793 "core_images" => $this->getUcmCoreAlias('images'), 3794 "core_urls" => $this->getUcmCoreAlias('urls'), 3795 "core_version" => $this->getUcmCoreAlias('version'), 3796 "core_ordering" => $this->getUcmCoreAlias('ordering'), 3797 "core_metakey" => $this->getUcmCoreAlias('metakey'), 3798 "core_metadesc" => $this->getUcmCoreAlias('metadesc'), 3799 "core_catid" => $this->getUcmCoreAlias('cat_id'), 3800 "core_xreference" => $this->getUcmCoreAlias('xreference'), 3801 "asset_id" => $this->getUcmCoreAlias('asset_id') 3802 ) 3803 ), 3804 'special' => array( 3805 0 => array( 3806 ) 3807 ) 3808 ) 3809 ); 3810 3811 $ignoreFields = array( 3812 $this->getUcmCoreAlias('modified_on', null), 3813 $this->getUcmCoreAlias('modified_by', null), 3814 $this->getUcmCoreAlias('locked_by', null), 3815 $this->getUcmCoreAlias('locked_on', null), 3816 $this->getUcmCoreAlias('hits', null), 3817 $this->getUcmCoreAlias('version', null) 3818 ); 3819 3820 $contentType->content_history_options = json_encode( 3821 array( 3822 "ignoreChanges" => array_filter($ignoreFields, 'strlen') 3823 ) 3824 ); 3825 3826 $contentType->router = ''; 3827 3828 $contentType->store(); 3829 } 3830 } 3831 3832 /** 3833 * Utility methods that fetches the column name for the field. 3834 * If it does not exists, returns a "null" string 3835 * 3836 * @param string $alias The alias for the column 3837 * @param string $null What to return if no column exists 3838 * 3839 * @return string The column name 3840 */ 3841 protected function getUcmCoreAlias($alias, $null = "null") 3842 { 3843 $alias = $this->getColumnAlias($alias); 3844 3845 if (in_array($alias, $this->getKnownFields())) 3846 { 3847 return $alias; 3848 } 3849 3850 return $null; 3851 } 3852} 3853