· 8 years ago · Feb 08, 2018, 07:54 PM
1<?php
2/**
3 * @package Joomla.Legacy
4 * @subpackage Model
5 *
6 * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 */
9
10defined('JPATH_PLATFORM') or die;
11
12use Joomla\Registry\Registry;
13
14/**
15 * Prototype admin model.
16 *
17 * @since 12.2
18 */
19abstract class JModelAdmin extends JModelForm
20{
21 /**
22 * The prefix to use with controller messages.
23 *
24 * @var string
25 * @since 12.2
26 */
27 protected $text_prefix = null;
28
29 /**
30 * The event to trigger after deleting the data.
31 *
32 * @var string
33 * @since 12.2
34 */
35 protected $event_after_delete = null;
36
37 /**
38 * The event to trigger after saving the data.
39 *
40 * @var string
41 * @since 12.2
42 */
43 protected $event_after_save = null;
44
45 /**
46 * The event to trigger before deleting the data.
47 *
48 * @var string
49 * @since 12.2
50 */
51 protected $event_before_delete = null;
52
53 /**
54 * The event to trigger before saving the data.
55 *
56 * @var string
57 * @since 12.2
58 */
59 protected $event_before_save = null;
60
61 /**
62 * The event to trigger after changing the published state of the data.
63 *
64 * @var string
65 * @since 12.2
66 */
67 protected $event_change_state = null;
68
69 /**
70 * Batch copy/move command. If set to false,
71 * the batch copy/move command is not supported
72 *
73 * @var string
74 */
75 protected $batch_copymove = 'category_id';
76
77 /**
78 * Allowed batch commands
79 *
80 * @var array
81 */
82 protected $batch_commands = array(
83 'assetgroup_id' => 'batchAccess',
84 'language_id' => 'batchLanguage',
85 'tag' => 'batchTag',
86 );
87
88 /**
89 * The context used for the associations table
90 *
91 * @var string
92 * @since 3.4.4
93 */
94 protected $associationsContext = null;
95
96 /**
97 * Constructor.
98 *
99 * @param array $config An optional associative array of configuration settings.
100 *
101 * @see JModelLegacy
102 * @since 12.2
103 */
104 public function __construct($config = array())
105 {
106 parent::__construct($config);
107
108 if (isset($config['event_after_delete']))
109 {
110 $this->event_after_delete = $config['event_after_delete'];
111 }
112 elseif (empty($this->event_after_delete))
113 {
114 $this->event_after_delete = 'onContentAfterDelete';
115 }
116
117 if (isset($config['event_after_save']))
118 {
119 $this->event_after_save = $config['event_after_save'];
120 }
121 elseif (empty($this->event_after_save))
122 {
123 $this->event_after_save = 'onContentAfterSave';
124 }
125
126 if (isset($config['event_before_delete']))
127 {
128 $this->event_before_delete = $config['event_before_delete'];
129 }
130 elseif (empty($this->event_before_delete))
131 {
132 $this->event_before_delete = 'onContentBeforeDelete';
133 }
134
135 if (isset($config['event_before_save']))
136 {
137 $this->event_before_save = $config['event_before_save'];
138 }
139 elseif (empty($this->event_before_save))
140 {
141 $this->event_before_save = 'onContentBeforeSave';
142 }
143
144 if (isset($config['event_change_state']))
145 {
146 $this->event_change_state = $config['event_change_state'];
147 }
148 elseif (empty($this->event_change_state))
149 {
150 $this->event_change_state = 'onContentChangeState';
151 }
152
153 $config['events_map'] = isset($config['events_map']) ? $config['events_map'] : array();
154
155 $this->events_map = array_merge(
156 array(
157 'delete' => 'content',
158 'save' => 'content',
159 'change_state' => 'content',
160 'validate' => 'content',
161 ), $config['events_map']
162 );
163
164 // Guess the JText message prefix. Defaults to the option.
165 if (isset($config['text_prefix']))
166 {
167 $this->text_prefix = strtoupper($config['text_prefix']);
168 }
169 elseif (empty($this->text_prefix))
170 {
171 $this->text_prefix = strtoupper($this->option);
172 }
173 }
174
175 /**
176 * Method to perform batch operations on an item or a set of items.
177 *
178 * @param array $commands An array of commands to perform.
179 * @param array $pks An array of item ids.
180 * @param array $contexts An array of item contexts.
181 *
182 * @return boolean Returns true on success, false on failure.
183 *
184 * @since 12.2
185 */
186 public function batch($commands, $pks, $contexts)
187 {
188 // Sanitize ids.
189 $pks = array_unique($pks);
190 JArrayHelper::toInteger($pks);
191
192 // Remove any values of zero.
193 if (array_search(0, $pks, true))
194 {
195 unset($pks[array_search(0, $pks, true)]);
196 }
197
198 if (empty($pks))
199 {
200 $this->setError(JText::_('JGLOBAL_NO_ITEM_SELECTED'));
201
202 return false;
203 }
204
205 $done = false;
206
207 // Set some needed variables.
208 $this->user = JFactory::getUser();
209 $this->table = $this->getTable();
210 $this->tableClassName = get_class($this->table);
211 $this->contentType = new JUcmType;
212 $this->type = $this->contentType->getTypeByTable($this->tableClassName);
213 $this->batchSet = true;
214
215 if ($this->type == false)
216 {
217 $type = new JUcmType;
218 $this->type = $type->getTypeByAlias($this->typeAlias);
219 }
220
221 $this->tagsObserver = $this->table->getObserverOfClass('JTableObserverTags');
222
223 if ($this->batch_copymove && !empty($commands[$this->batch_copymove]))
224 {
225 $cmd = JArrayHelper::getValue($commands, 'move_copy', 'c');
226
227 if ($cmd == 'c')
228 {
229 $result = $this->batchCopy($commands[$this->batch_copymove], $pks, $contexts);
230
231 if (is_array($result))
232 {
233 foreach ($result as $old => $new)
234 {
235 $contexts[$new] = $contexts[$old];
236 }
237 $pks = array_values($result);
238 }
239 else
240 {
241 return false;
242 }
243 }
244 elseif ($cmd == 'm' && !$this->batchMove($commands[$this->batch_copymove], $pks, $contexts))
245 {
246 return false;
247 }
248
249 $done = true;
250 }
251
252 foreach ($this->batch_commands as $identifier => $command)
253 {
254 if (strlen($commands[$identifier]) > 0)
255 {
256 if (!$this->$command($commands[$identifier], $pks, $contexts))
257 {
258 return false;
259 }
260
261 $done = true;
262 }
263 }
264
265 if (!$done)
266 {
267 $this->setError(JText::_('JLIB_APPLICATION_ERROR_INSUFFICIENT_BATCH_INFORMATION'));
268
269 return false;
270 }
271
272 // Clear the cache
273 $this->cleanCache();
274
275 return true;
276 }
277
278 /**
279 * Batch access level changes for a group of rows.
280 *
281 * @param integer $value The new value matching an Asset Group ID.
282 * @param array $pks An array of row IDs.
283 * @param array $contexts An array of item contexts.
284 *
285 * @return boolean True if successful, false otherwise and internal error is set.
286 *
287 * @since 12.2
288 */
289 protected function batchAccess($value, $pks, $contexts)
290 {
291 if (empty($this->batchSet))
292 {
293 // Set some needed variables.
294 $this->user = JFactory::getUser();
295 $this->table = $this->getTable();
296 $this->tableClassName = get_class($this->table);
297 $this->contentType = new JUcmType;
298 $this->type = $this->contentType->getTypeByTable($this->tableClassName);
299 }
300
301 foreach ($pks as $pk)
302 {
303 if ($this->user->authorise('core.edit', $contexts[$pk]))
304 {
305 $this->table->reset();
306 $this->table->load($pk);
307 $this->table->access = (int) $value;
308
309 if (!empty($this->type))
310 {
311 $this->createTagsHelper($this->tagsObserver, $this->type, $pk, $this->typeAlias, $this->table);
312 }
313
314 if (!$this->table->store())
315 {
316 $this->setError($this->table->getError());
317
318 return false;
319 }
320 }
321 else
322 {
323 $this->setError(JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
324
325 return false;
326 }
327 }
328
329 // Clean the cache
330 $this->cleanCache();
331
332 return true;
333 }
334
335 /**
336 * Batch copy items to a new category or current.
337 *
338 * @param integer $value The new category.
339 * @param array $pks An array of row IDs.
340 * @param array $contexts An array of item contexts.
341 *
342 * @return array|boolean An array of new IDs on success, boolean false on failure.
343 *
344 * @since 12.2
345 */
346 protected function batchCopy($value, $pks, $contexts)
347 {
348 if (empty($this->batchSet))
349 {
350 // Set some needed variables.
351 $this->user = JFactory::getUser();
352 $this->table = $this->getTable();
353 $this->tableClassName = get_class($this->table);
354 $this->contentType = new JUcmType;
355 $this->type = $this->contentType->getTypeByTable($this->tableClassName);
356 }
357
358 $categoryId = $value;
359
360 if (!$this->checkCategoryId($categoryId))
361 {
362 return false;
363 }
364
365 $newIds = array();
366
367 // Parent exists so let's proceed
368 while (!empty($pks))
369 {
370 // Pop the first ID off the stack
371 $pk = array_shift($pks);
372
373 $this->table->reset();
374
375 // Check that the row actually exists
376 if (!$this->table->load($pk))
377 {
378 if ($error = $this->table->getError())
379 {
380 // Fatal error
381 $this->setError($error);
382
383 return false;
384 }
385 else
386 {
387 // Not fatal error
388 $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_BATCH_MOVE_ROW_NOT_FOUND', $pk));
389 continue;
390 }
391 }
392
393 $this->generateTitle($categoryId, $this->table);
394
395 // Reset the ID because we are making a copy
396 $this->table->id = 0;
397
398 // Unpublish because we are making a copy
399 if (isset($this->table->published))
400 {
401 $this->table->published = 0;
402 }
403 elseif (isset($this->table->state))
404 {
405 $this->table->state = 0;
406 }
407
408 // New category ID
409 $this->table->catid = $categoryId;
410
411 // TODO: Deal with ordering?
412 // $this->table->ordering = 1;
413
414 // Check the row.
415 if (!$this->table->check())
416 {
417 $this->setError($this->table->getError());
418
419 return false;
420 }
421
422 if (!empty($this->type))
423 {
424 $this->createTagsHelper($this->tagsObserver, $this->type, $pk, $this->typeAlias, $this->table);
425 }
426
427 // Store the row.
428 if (!$this->table->store())
429 {
430 $this->setError($this->table->getError());
431
432 return false;
433 }
434
435 // Get the new item ID
436 $newId = $this->table->get('id');
437
438 // Add the new ID to the array
439 $newIds[$pk] = $newId;
440 }
441
442 // Clean the cache
443 $this->cleanCache();
444
445 return $newIds;
446 }
447
448 /**
449 * Batch language changes for a group of rows.
450 *
451 * @param string $value The new value matching a language.
452 * @param array $pks An array of row IDs.
453 * @param array $contexts An array of item contexts.
454 *
455 * @return boolean True if successful, false otherwise and internal error is set.
456 *
457 * @since 11.3
458 */
459 protected function batchLanguage($value, $pks, $contexts)
460 {
461 if (empty($this->batchSet))
462 {
463 // Set some needed variables.
464 $this->user = JFactory::getUser();
465 $this->table = $this->getTable();
466 $this->tableClassName = get_class($this->table);
467 $this->contentType = new JUcmType;
468 $this->type = $this->contentType->getTypeByTable($this->tableClassName);
469 }
470
471 foreach ($pks as $pk)
472 {
473 if ($this->user->authorise('core.edit', $contexts[$pk]))
474 {
475 $this->table->reset();
476 $this->table->load($pk);
477 $this->table->language = $value;
478
479 if (!empty($this->type))
480 {
481 $this->createTagsHelper($this->tagsObserver, $this->type, $pk, $this->typeAlias, $this->table);
482 }
483
484 if (!$this->table->store())
485 {
486 $this->setError($this->table->getError());
487
488 return false;
489 }
490 }
491 else
492 {
493 $this->setError(JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
494
495 return false;
496 }
497 }
498
499 // Clean the cache
500 $this->cleanCache();
501
502 return true;
503 }
504
505 /**
506 * Batch move items to a new category
507 *
508 * @param integer $value The new category ID.
509 * @param array $pks An array of row IDs.
510 * @param array $contexts An array of item contexts.
511 *
512 * @return boolean True if successful, false otherwise and internal error is set.
513 *
514 * @since 12.2
515 */
516 protected function batchMove($value, $pks, $contexts)
517 {
518 if (empty($this->batchSet))
519 {
520 // Set some needed variables.
521 $this->user = JFactory::getUser();
522 $this->table = $this->getTable();
523 $this->tableClassName = get_class($this->table);
524 $this->contentType = new JUcmType;
525 $this->type = $this->contentType->getTypeByTable($this->tableClassName);
526 }
527
528 $categoryId = (int) $value;
529
530 if (!$this->checkCategoryId($categoryId))
531 {
532 return false;
533 }
534
535 // Parent exists so we proceed
536 foreach ($pks as $pk)
537 {
538 if (!$this->user->authorise('core.edit', $contexts[$pk]))
539 {
540 $this->setError(JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
541
542 return false;
543 }
544
545 // Check that the row actually exists
546 if (!$this->table->load($pk))
547 {
548 if ($error = $this->table->getError())
549 {
550 // Fatal error
551 $this->setError($error);
552
553 return false;
554 }
555 else
556 {
557 // Not fatal error
558 $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_BATCH_MOVE_ROW_NOT_FOUND', $pk));
559 continue;
560 }
561 }
562
563 // Set the new category ID
564 $this->table->catid = $categoryId;
565
566 // Check the row.
567 if (!$this->table->check())
568 {
569 $this->setError($this->table->getError());
570
571 return false;
572 }
573
574 if (!empty($this->type))
575 {
576 $this->createTagsHelper($this->tagsObserver, $this->type, $pk, $this->typeAlias, $this->table);
577 }
578
579 // Store the row.
580 if (!$this->table->store())
581 {
582 $this->setError($this->table->getError());
583
584 return false;
585 }
586 }
587
588 // Clean the cache
589 $this->cleanCache();
590
591 return true;
592 }
593
594 /**
595 * Batch tag a list of item.
596 *
597 * @param integer $value The value of the new tag.
598 * @param array $pks An array of row IDs.
599 * @param array $contexts An array of item contexts.
600 *
601 * @return boolean True if successful, false otherwise and internal error is set.
602 *
603 * @since 3.1
604 */
605 protected function batchTag($value, $pks, $contexts)
606 {
607 // Set the variables
608 $user = JFactory::getUser();
609 $table = $this->getTable();
610
611 foreach ($pks as $pk)
612 {
613 if ($user->authorise('core.edit', $contexts[$pk]))
614 {
615 $table->reset();
616 $table->load($pk);
617 $tags = array($value);
618
619 /**
620 * @var JTableObserverTags $tagsObserver
621 */
622 $tagsObserver = $table->getObserverOfClass('JTableObserverTags');
623 $result = $tagsObserver->setNewTags($tags, false);
624
625 if (!$result)
626 {
627 $this->setError($table->getError());
628
629 return false;
630 }
631 }
632 else
633 {
634 $this->setError(JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
635
636 return false;
637 }
638 }
639
640 // Clean the cache
641 $this->cleanCache();
642
643 return true;
644 }
645
646 /**
647 * Method to test whether a record can be deleted.
648 *
649 * @param object $record A record object.
650 *
651 * @return boolean True if allowed to delete the record. Defaults to the permission for the component.
652 *
653 * @since 12.2
654 */
655 protected function canDelete($record)
656 {
657 return JFactory::getUser()->authorise('core.delete', $this->option);
658 }
659
660 /**
661 * Method to test whether a record can be deleted.
662 *
663 * @param object $record A record object.
664 *
665 * @return boolean True if allowed to change the state of the record. Defaults to the permission for the component.
666 *
667 * @since 12.2
668 */
669 protected function canEditState($record)
670 {
671 return JFactory::getUser()->authorise('core.edit.state', $this->option);
672 }
673
674 /**
675 * Method override to check-in a record or an array of record
676 *
677 * @param mixed $pks The ID of the primary key or an array of IDs
678 *
679 * @return integer|boolean Boolean false if there is an error, otherwise the count of records checked in.
680 *
681 * @since 12.2
682 */
683 public function checkin($pks = array())
684 {
685 $pks = (array) $pks;
686 $table = $this->getTable();
687 $count = 0;
688
689 if (empty($pks))
690 {
691 $pks = array((int) $this->getState($this->getName() . '.id'));
692 }
693
694 // Check in all items.
695 foreach ($pks as $pk)
696 {
697 if ($table->load($pk))
698 {
699 if ($table->checked_out > 0)
700 {
701 if (!parent::checkin($pk))
702 {
703 return false;
704 }
705
706 $count++;
707 }
708 }
709 else
710 {
711 $this->setError($table->getError());
712
713 return false;
714 }
715 }
716
717 return $count;
718 }
719
720 /**
721 * Method override to check-out a record.
722 *
723 * @param integer $pk The ID of the primary key.
724 *
725 * @return boolean True if successful, false if an error occurs.
726 *
727 * @since 12.2
728 */
729 public function checkout($pk = null)
730 {
731 $pk = (!empty($pk)) ? $pk : (int) $this->getState($this->getName() . '.id');
732
733 return parent::checkout($pk);
734 }
735
736 /**
737 * Method to delete one or more records.
738 *
739 * @param array &$pks An array of record primary keys.
740 *
741 * @return boolean True if successful, false if an error occurs.
742 *
743 * @since 12.2
744 */
745 public function delete(&$pks)
746 {
747 $dispatcher = JEventDispatcher::getInstance();
748 $pks = (array) $pks;
749 $table = $this->getTable();
750
751 // Include the plugins for the delete events.
752 JPluginHelper::importPlugin($this->events_map['delete']);
753
754 // Iterate the items to delete each one.
755 foreach ($pks as $i => $pk)
756 {
757 if ($table->load($pk))
758 {
759 if ($this->canDelete($table))
760 {
761 $context = $this->option . '.' . $this->name;
762
763 // Trigger the before delete event.
764 $result = $dispatcher->trigger($this->event_before_delete, array($context, $table));
765
766 if (in_array(false, $result, true))
767 {
768 $this->setError($table->getError());
769
770 return false;
771 }
772
773 // Multilanguage: if associated, delete the item in the _associations table
774 if ($this->associationsContext && JLanguageAssociations::isEnabled())
775 {
776 $db = $this->getDbo();
777 $query = $db->getQuery(true)
778 ->select('COUNT(*) as count, ' . $db->quoteName('as1.key'))
779 ->from($db->quoteName('#__associations') . ' AS as1')
780 ->join('LEFT', $db->quoteName('#__associations') . ' AS as2 ON ' . $db->quoteName('as1.key') . ' = ' . $db->quoteName('as2.key'))
781 ->where($db->quoteName('as1.context') . ' = ' . $db->quote($this->associationsContext))
782 ->where($db->quoteName('as1.id') . ' = ' . (int) $pk)
783 ->group($db->quoteName('as1.key'));
784
785 $db->setQuery($query);
786 $row = $db->loadAssoc();
787
788 if (!empty($row['count']))
789 {
790 $query = $db->getQuery(true)
791 ->delete($db->quoteName('#__associations'))
792 ->where($db->quoteName('context') . ' = ' . $db->quote($this->associationsContext))
793 ->where($db->quoteName('key') . ' = ' . $db->quote($row['key']));
794
795 if ($row['count'] > 2)
796 {
797 $query->where($db->quoteName('id') . ' = ' . (int) $pk);
798 }
799
800 $db->setQuery($query);
801 $db->execute();
802 }
803 }
804
805 if (!$table->delete($pk))
806 {
807 $this->setError($table->getError());
808
809 return false;
810 }
811
812 // Trigger the after event.
813 $dispatcher->trigger($this->event_after_delete, array($context, $table));
814 }
815 else
816 {
817 // Prune items that you can't change.
818 unset($pks[$i]);
819 $error = $this->getError();
820
821 if ($error)
822 {
823 JLog::add($error, JLog::WARNING, 'jerror');
824
825 return false;
826 }
827 else
828 {
829 JLog::add(JText::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), JLog::WARNING, 'jerror');
830
831 return false;
832 }
833 }
834 }
835 else
836 {
837 $this->setError($table->getError());
838
839 return false;
840 }
841 }
842
843 // Clear the component's cache
844 $this->cleanCache();
845
846 return true;
847 }
848
849 /**
850 * Method to change the title & alias.
851 *
852 * @param integer $category_id The id of the category.
853 * @param string $alias The alias.
854 * @param string $title The title.
855 *
856 * @return array Contains the modified title and alias.
857 *
858 * @since 12.2
859 */
860 protected function generateNewTitle($category_id, $alias, $title)
861 {
862 // Alter the title & alias
863 $table = $this->getTable();
864
865 while ($table->load(array('alias' => $alias, 'catid' => $category_id)))
866 {
867 $title = JString::increment($title);
868 $alias = JString::increment($alias, 'dash');
869 }
870
871 return array($title, $alias);
872 }
873
874 /**
875 * Method to get a single record.
876 *
877 * @param integer $pk The id of the primary key.
878 *
879 * @return JObject|boolean Object on success, false on failure.
880 *
881 * @since 12.2
882 */
883 public function getItem($pk = null)
884 {
885 // exit('ITAD');
886 $pk = (!empty($pk)) ? $pk : (int) $this->getState($this->getName() . '.id');
887 // jimport( 'joomla.application.component.model' );
888 jimport( 'joomla.database.table' );
889 // jimport( 'joomla.application.component.model' );
890 // JTable::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_recipes/tables');
891
892 $table = $this->getTable();
893
894 echo 'table = '.$table;
895 print_r($table);
896 exit('<br>ITAD: '.JPATH_ADMINISTRATOR);
897
898 if ($pk > 0)
899 {
900 // Attempt to load the row.
901 // exit('ITAD');
902
903 $return = $table->load($pk);
904 exit('ITAD');
905
906 // Check for a table object error.
907 if ($return === false && $table->getError())
908 {
909 $this->setError($table->getError());
910
911 return false;
912 }
913 }
914
915 // Convert to the JObject before adding other data.
916 $properties = $table->getProperties(1);
917 $item = JArrayHelper::toObject($properties, 'JObject');
918
919 if (property_exists($item, 'params'))
920 {
921 $registry = new Registry;
922 $registry->loadString($item->params);
923 $item->params = $registry->toArray();
924 }
925
926 return $item;
927 }
928
929 /**
930 * A protected method to get a set of ordering conditions.
931 *
932 * @param JTable $table A JTable object.
933 *
934 * @return array An array of conditions to add to ordering queries.
935 *
936 * @since 12.2
937 */
938 protected function getReorderConditions($table)
939 {
940 return array();
941 }
942
943 /**
944 * Stock method to auto-populate the model state.
945 *
946 * @return void
947 *
948 * @since 12.2
949 */
950 protected function populateState()
951 {
952 $table = $this->getTable();
953 $key = $table->getKeyName();
954
955 // Get the pk of the record from the request.
956 $pk = JFactory::getApplication()->input->getInt($key);
957 $this->setState($this->getName() . '.id', $pk);
958
959 // Load the parameters.
960 $value = JComponentHelper::getParams($this->option);
961 $this->setState('params', $value);
962 }
963
964 /**
965 * Prepare and sanitise the table data prior to saving.
966 *
967 * @param JTable $table A reference to a JTable object.
968 *
969 * @return void
970 *
971 * @since 12.2
972 */
973 protected function prepareTable($table)
974 {
975 // Derived class will provide its own implementation if required.
976 }
977
978 /**
979 * Method to change the published state of one or more records.
980 *
981 * @param array &$pks A list of the primary keys to change.
982 * @param integer $value The value of the published state.
983 *
984 * @return boolean True on success.
985 *
986 * @since 12.2
987 */
988 public function publish(&$pks, $value = 1)
989 {
990 $dispatcher = JEventDispatcher::getInstance();
991 $user = JFactory::getUser();
992 $table = $this->getTable();
993 $pks = (array) $pks;
994
995 // Include the plugins for the change of state event.
996 JPluginHelper::importPlugin($this->events_map['change_state']);
997
998 // Access checks.
999 foreach ($pks as $i => $pk)
1000 {
1001 $table->reset();
1002
1003 if ($table->load($pk))
1004 {
1005 if (!$this->canEditState($table))
1006 {
1007 // Prune items that you can't change.
1008 unset($pks[$i]);
1009
1010 JLog::add(JText::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), JLog::WARNING, 'jerror');
1011
1012 return false;
1013 }
1014
1015 // If the table is checked out by another user, drop it and report to the user trying to change its state.
1016 if (property_exists($table, 'checked_out') && $table->checked_out && ($table->checked_out != $user->id))
1017 {
1018 JLog::add(JText::_('JLIB_APPLICATION_ERROR_CHECKIN_USER_MISMATCH'), JLog::WARNING, 'jerror');
1019
1020 // Prune items that you can't change.
1021 unset($pks[$i]);
1022
1023 return false;
1024 }
1025 }
1026 }
1027
1028 // Attempt to change the state of the records.
1029 if (!$table->publish($pks, $value, $user->get('id')))
1030 {
1031 $this->setError($table->getError());
1032
1033 return false;
1034 }
1035
1036 $context = $this->option . '.' . $this->name;
1037
1038 // Trigger the change state event.
1039 $result = $dispatcher->trigger($this->event_change_state, array($context, $pks, $value));
1040
1041 if (in_array(false, $result, true))
1042 {
1043 $this->setError($table->getError());
1044
1045 return false;
1046 }
1047
1048 // Clear the component's cache
1049 $this->cleanCache();
1050
1051 return true;
1052 }
1053
1054 /**
1055 * Method to adjust the ordering of a row.
1056 *
1057 * Returns NULL if the user did not have edit
1058 * privileges for any of the selected primary keys.
1059 *
1060 * @param integer $pks The ID of the primary key to move.
1061 * @param integer $delta Increment, usually +1 or -1
1062 *
1063 * @return boolean|null False on failure or error, true on success, null if the $pk is empty (no items selected).
1064 *
1065 * @since 12.2
1066 */
1067 public function reorder($pks, $delta = 0)
1068 {
1069 $table = $this->getTable();
1070 $pks = (array) $pks;
1071 $result = true;
1072
1073 $allowed = true;
1074
1075 foreach ($pks as $i => $pk)
1076 {
1077 $table->reset();
1078
1079 if ($table->load($pk) && $this->checkout($pk))
1080 {
1081 // Access checks.
1082 if (!$this->canEditState($table))
1083 {
1084 // Prune items that you can't change.
1085 unset($pks[$i]);
1086 $this->checkin($pk);
1087 JLog::add(JText::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), JLog::WARNING, 'jerror');
1088 $allowed = false;
1089 continue;
1090 }
1091
1092 $where = $this->getReorderConditions($table);
1093
1094 if (!$table->move($delta, $where))
1095 {
1096 $this->setError($table->getError());
1097 unset($pks[$i]);
1098 $result = false;
1099 }
1100
1101 $this->checkin($pk);
1102 }
1103 else
1104 {
1105 $this->setError($table->getError());
1106 unset($pks[$i]);
1107 $result = false;
1108 }
1109 }
1110
1111 if ($allowed === false && empty($pks))
1112 {
1113 $result = null;
1114 }
1115
1116 // Clear the component's cache
1117 if ($result == true)
1118 {
1119 $this->cleanCache();
1120 }
1121
1122 return $result;
1123 }
1124
1125 /**
1126 * Method to save the form data.
1127 *
1128 * @param array $data The form data.
1129 *
1130 * @return boolean True on success, False on error.
1131 *
1132 * @since 12.2
1133 */
1134 public function save($data)
1135 {
1136 $dispatcher = JEventDispatcher::getInstance();
1137 $table = $this->getTable();
1138 $context = $this->option . '.' . $this->name;
1139
1140 if ((!empty($data['tags']) && $data['tags'][0] != ''))
1141 {
1142 $table->newTags = $data['tags'];
1143 }
1144
1145 $key = $table->getKeyName();
1146 $pk = (!empty($data[$key])) ? $data[$key] : (int) $this->getState($this->getName() . '.id');
1147 $isNew = true;
1148
1149 // Include the plugins for the save events.
1150 JPluginHelper::importPlugin($this->events_map['save']);
1151
1152 // Allow an exception to be thrown.
1153 try
1154 {
1155 // Load the row if saving an existing record.
1156 if ($pk > 0)
1157 {
1158 $table->load($pk);
1159 $isNew = false;
1160 }
1161
1162 // Bind the data.
1163 if (!$table->bind($data))
1164 {
1165 $this->setError($table->getError());
1166
1167 return false;
1168 }
1169
1170 // Prepare the row for saving
1171 $this->prepareTable($table);
1172
1173 // Check the data.
1174 if (!$table->check())
1175 {
1176 $this->setError($table->getError());
1177
1178 return false;
1179 }
1180
1181 // Trigger the before save event.
1182 $result = $dispatcher->trigger($this->event_before_save, array($context, $table, $isNew));
1183
1184 if (in_array(false, $result, true))
1185 {
1186 $this->setError($table->getError());
1187
1188 return false;
1189 }
1190
1191 // Store the data.
1192 if (!$table->store())
1193 {
1194 $this->setError($table->getError());
1195
1196 return false;
1197 }
1198
1199 // Clean the cache.
1200 $this->cleanCache();
1201
1202 // Trigger the after save event.
1203 $dispatcher->trigger($this->event_after_save, array($context, $table, $isNew));
1204 }
1205 catch (Exception $e)
1206 {
1207 $this->setError($e->getMessage());
1208
1209 return false;
1210 }
1211
1212 if (isset($table->$key))
1213 {
1214 $this->setState($this->getName() . '.id', $table->$key);
1215 }
1216
1217 $this->setState($this->getName() . '.new', $isNew);
1218
1219 if ($this->associationsContext && JLanguageAssociations::isEnabled() && !empty($data['associations']))
1220 {
1221 $associations = $data['associations'];
1222
1223 // Unset any invalid associations
1224 $associations = Joomla\Utilities\ArrayHelper::toInteger($associations);
1225
1226 // Unset any invalid associations
1227 foreach ($associations as $tag => $id)
1228 {
1229 if (!$id)
1230 {
1231 unset($associations[$tag]);
1232 }
1233 }
1234
1235 // Show a warning if the item isn't assigned to a language but we have associations.
1236 if ($associations && ($table->language == '*'))
1237 {
1238 JFactory::getApplication()->enqueueMessage(
1239 JText::_(strtoupper($this->option) . '_ERROR_ALL_LANGUAGE_ASSOCIATED'),
1240 'warning'
1241 );
1242 }
1243
1244 // Get associationskey for edited item
1245 $db = $this->getDbo();
1246 $query = $db->getQuery(true)
1247 ->select($db->qn('key'))
1248 ->from($db->qn('#__associations'))
1249 ->where($db->qn('context') . ' = ' . $db->quote($this->associationsContext))
1250 ->where($db->qn('id') . ' = ' . (int) $table->$key);
1251 $db->setQuery($query);
1252 $old_key = $db->loadResult();
1253
1254 // Deleting old associations for the associated items
1255 $query = $db->getQuery(true)
1256 ->delete($db->qn('#__associations'))
1257 ->where($db->qn('context') . ' = ' . $db->quote($this->associationsContext));
1258
1259 if ($associations)
1260 {
1261 $query->where('(' . $db->qn('id') . ' IN (' . implode(',', $associations) . ') OR '
1262 . $db->qn('key') . ' = ' . $db->q($old_key) . ')');
1263 }
1264 else
1265 {
1266 $query->where($db->qn('key') . ' = ' . $db->q($old_key));
1267 }
1268
1269 $db->setQuery($query);
1270 $db->execute();
1271
1272 // Adding self to the association
1273 if ($table->language != '*')
1274 {
1275 $associations[$table->language] = (int) $table->$key;
1276 }
1277
1278 if ((count($associations)) > 1)
1279 {
1280 // Adding new association for these items
1281 $key = md5(json_encode($associations));
1282 $query = $db->getQuery(true)
1283 ->insert('#__associations');
1284
1285 foreach ($associations as $id)
1286 {
1287 $query->values(((int) $id) . ',' . $db->quote($this->associationsContext) . ',' . $db->quote($key));
1288 }
1289
1290 $db->setQuery($query);
1291 $db->execute();
1292 }
1293 }
1294
1295 return true;
1296 }
1297
1298 /**
1299 * Saves the manually set order of records.
1300 *
1301 * @param array $pks An array of primary key ids.
1302 * @param integer $order +1 or -1
1303 *
1304 * @return boolean|JException Boolean true on success, false on failure, or JException if no items are selected
1305 *
1306 * @since 12.2
1307 */
1308 public function saveorder($pks = array(), $order = null)
1309 {
1310 $table = $this->getTable();
1311 $tableClassName = get_class($table);
1312 $contentType = new JUcmType;
1313 $type = $contentType->getTypeByTable($tableClassName);
1314 $tagsObserver = $table->getObserverOfClass('JTableObserverTags');
1315 $conditions = array();
1316
1317 if (empty($pks))
1318 {
1319 return JError::raiseWarning(500, JText::_($this->text_prefix . '_ERROR_NO_ITEMS_SELECTED'));
1320 }
1321
1322 // Update ordering values
1323 foreach ($pks as $i => $pk)
1324 {
1325 $table->load((int) $pk);
1326
1327 // Access checks.
1328 if (!$this->canEditState($table))
1329 {
1330 // Prune items that you can't change.
1331 unset($pks[$i]);
1332 JLog::add(JText::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), JLog::WARNING, 'jerror');
1333 }
1334 elseif ($table->ordering != $order[$i])
1335 {
1336 $table->ordering = $order[$i];
1337
1338 if ($type)
1339 {
1340 $this->createTagsHelper($tagsObserver, $type, $pk, $type->type_alias, $table);
1341 }
1342
1343 if (!$table->store())
1344 {
1345 $this->setError($table->getError());
1346
1347 return false;
1348 }
1349
1350 // Remember to reorder within position and client_id
1351 $condition = $this->getReorderConditions($table);
1352 $found = false;
1353
1354 foreach ($conditions as $cond)
1355 {
1356 if ($cond[1] == $condition)
1357 {
1358 $found = true;
1359 break;
1360 }
1361 }
1362
1363 if (!$found)
1364 {
1365 $key = $table->getKeyName();
1366 $conditions[] = array($table->$key, $condition);
1367 }
1368 }
1369 }
1370
1371 // Execute reorder for each category.
1372 foreach ($conditions as $cond)
1373 {
1374 $table->load($cond[0]);
1375 $table->reorder($cond[1]);
1376 }
1377
1378 // Clear the component's cache
1379 $this->cleanCache();
1380
1381 return true;
1382 }
1383
1384 /**
1385 * Method to create a tags helper to ensure proper management of tags
1386 *
1387 * @param JTableObserverTags $tagsObserver The tags observer for this table
1388 * @param JUcmType $type The type for the table being processed
1389 * @param integer $pk Primary key of the item bing processed
1390 * @param string $typeAlias The type alias for this table
1391 * @param JTable $table The JTable object
1392 *
1393 * @return void
1394 *
1395 * @since 3.2
1396 */
1397 public function createTagsHelper($tagsObserver, $type, $pk, $typeAlias, $table)
1398 {
1399 if (!empty($tagsObserver) && !empty($type))
1400 {
1401 $table->tagsHelper = new JHelperTags;
1402 $table->tagsHelper->typeAlias = $typeAlias;
1403 $table->tagsHelper->tags = explode(',', $table->tagsHelper->getTagIds($pk, $typeAlias));
1404 }
1405 }
1406
1407 /**
1408 * Method to check the validity of the category ID for batch copy and move
1409 *
1410 * @param integer $categoryId The category ID to check
1411 *
1412 * @return boolean
1413 *
1414 * @since 3.2
1415 */
1416 protected function checkCategoryId($categoryId)
1417 {
1418 // Check that the category exists
1419 if ($categoryId)
1420 {
1421 $categoryTable = JTable::getInstance('Category');
1422
1423 if (!$categoryTable->load($categoryId))
1424 {
1425 if ($error = $categoryTable->getError())
1426 {
1427 // Fatal error
1428 $this->setError($error);
1429
1430 return false;
1431 }
1432 else
1433 {
1434 $this->setError(JText::_('JLIB_APPLICATION_ERROR_BATCH_MOVE_CATEGORY_NOT_FOUND'));
1435
1436 return false;
1437 }
1438 }
1439 }
1440
1441 if (empty($categoryId))
1442 {
1443 $this->setError(JText::_('JLIB_APPLICATION_ERROR_BATCH_MOVE_CATEGORY_NOT_FOUND'));
1444
1445 return false;
1446 }
1447
1448 // Check that the user has create permission for the component
1449 $extension = JFactory::getApplication()->input->get('option', '');
1450
1451 if (!$this->user->authorise('core.create', $extension . '.category.' . $categoryId))
1452 {
1453 $this->setError(JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_CREATE'));
1454
1455 return false;
1456 }
1457
1458 return true;
1459 }
1460
1461 /**
1462 * A method to preprocess generating a new title in order to allow tables with alternative names
1463 * for alias and title to use the batch move and copy methods
1464 *
1465 * @param integer $categoryId The target category id
1466 * @param JTable $table The JTable within which move or copy is taking place
1467 *
1468 * @return void
1469 *
1470 * @since 3.2
1471 */
1472 public function generateTitle($categoryId, $table)
1473 {
1474 // Alter the title & alias
1475 $data = $this->generateNewTitle($categoryId, $table->alias, $table->title);
1476 $table->title = $data['0'];
1477 $table->alias = $data['1'];
1478 }
1479}