1<?php 2/* 3 * e107 website system 4 * 5 * Copyright (C) 2008-2012 e107 Inc (e107.org) 6 * Released under the terms and conditions of the 7 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) 8 * 9 * Administration User Interface logic 10 * 11 * $URL$ 12 * $Id$ 13 */ 14 15 /** 16 * @package e107 17 * @subpackage e107_handlers 18 * @version $Id$ 19 * 20 * Administration User Interface logic 21 */ 22 23 24/** 25 * @todo core request handler (non-admin), core response 26 */ 27if (!defined('e107_INIT')){ exit; } 28 29 30class e_admin_request 31{ 32 /** 33 * Current GET request array 34 * @var array 35 */ 36 protected $_request_qry; 37 38 /** 39 * Current POST array 40 * @var array 41 */ 42 protected $_posted_qry; 43 44 /** 45 * Current Mode 46 * @var string 47 */ 48 protected $_mode = ''; 49 50 /** 51 * Default Mode 52 * @var string 53 */ 54 protected $_default_mode = 'main'; 55 56 /** 57 * Key name for mode search 58 * @var string 59 */ 60 protected $_mode_key = 'mode'; 61 62 /** 63 * Current action 64 * @var string 65 */ 66 protected $_action = ''; 67 68 /** 69 * Default Action 70 * @var string 71 */ 72 protected $_default_action = 'index'; 73 74 /** 75 * Key name for action search 76 * @var string 77 */ 78 protected $_action_key = 'action'; 79 80 /** 81 * Current ID 82 * @var integer 83 */ 84 protected $_id = 0; 85 86 /** 87 * Key name for ID search 88 * @var string 89 */ 90 protected $_id_key = 'id'; 91 92 /** 93 * Constructor 94 * 95 * @param string|array $qry [optional] 96 * @return none 97 */ 98 public function __construct($request_string = null, $parse = true) 99 { 100 if(null === $request_string) 101 { 102 $request_string = str_replace('&', '&', e_QUERY); 103 } 104 if($parse) 105 { 106 $this->parseRequest($request_string); 107 } 108 } 109 110 /** 111 * Parse request data 112 * @param string|array $request_data 113 * @return e_admin_request 114 */ 115 protected function parseRequest($request_data) 116 { 117 if(is_string($request_data)) 118 { 119 parse_str($request_data, $request_data); 120 } 121 $this->_request_qry = (array) $request_data; 122 123 // Set current mode 124 if(isset($this->_request_qry[$this->_mode_key])) 125 { 126 $this->_mode = preg_replace('/[^\w]/', '', $this->_request_qry[$this->_mode_key]); 127 } 128 129 // Set current action 130 if(isset($this->_request_qry[$this->_action_key])) 131 { 132 $this->_action = preg_replace('/[^\w]/', '', $this->_request_qry[$this->_action_key]); 133 } 134 135 // Set current id 136 if(isset($this->_request_qry[$this->_id_key])) 137 { 138 $this->_id = preg_replace('/[^\w\-:\.]/', '', $this->_request_qry[$this->_id_key]); 139 } 140 141 $this->_posted_qry =& $_POST; //raw? 142 143 return $this; 144 } 145 146 /** 147 * Retrieve variable from GET scope 148 * If $key is null, all GET data will be returned 149 * 150 * @param string $key [optional] 151 * @param mixed $default [optional] 152 * @return mixed 153 */ 154 public function getQuery($key = null, $default = null) 155 { 156 if(null === $key) 157 { 158 return $this->_request_qry; 159 } 160 return (isset($this->_request_qry[$key]) ? $this->_request_qry[$key] : $default); 161 } 162 163 /** 164 * Set/Unset GET variable 165 * If $key is array, $value is not used. 166 * If $value is null, (string) $key is unset 167 * 168 * @param string|array $key 169 * @param mixed $value [optional] 170 * @return e_admin_request 171 */ 172 public function setQuery($key, $value = null) 173 { 174 if(is_array($key)) 175 { 176 foreach ($key as $k=>$v) 177 { 178 $this->setQuery($k, $v); 179 } 180 return $this; 181 } 182 183 if(null === $value) 184 { 185 unset($this->_request_qry[$key]); 186 unset($_GET[$key]); 187 return $this; 188 } 189 190 $this->_request_qry[$key] = $value; 191 $_GET[$key] = $value; 192 return $this; 193 } 194 195 /** 196 * Retrieve variable from POST scope 197 * If $key is null, all POST data will be returned 198 * 199 * @param string $key [optional] 200 * @param mixed $default [optional] 201 * @return mixed 202 */ 203 public function getPosted($key = null, $default = null) 204 { 205 if(null === $key) 206 { 207 return $this->_posted_qry; 208 } 209 return (isset($this->_posted_qry[$key]) ? $this->_posted_qry[$key] : $default); 210 } 211 212 /** 213 * Set/Unset POST variable 214 * If $key is array, $value is not used. 215 * If $value is null, (string) $key is unset 216 * 217 * @param object $key 218 * @param object $value [optional] 219 * @return e_admin_request 220 */ 221 public function setPosted($key, $value = null) 222 { 223 if(is_array($key)) 224 { 225 if(empty($key)) 226 { 227 $this->_posted_qry = array(); //POST reset 228 return $this; 229 } 230 foreach ($key as $k=>$v) 231 { 232 $this->setPosted($k, $v); 233 } 234 return $this; 235 } 236 237 if(null === $value) 238 { 239 unset($this->_posted_qry[$key]); 240 return $this; 241 } 242 243 $tp = e107::getParser(); 244 $this->_posted_qry[$tp->post_toForm($key)] = $tp->post_toForm($value); 245 return $this; 246 } 247 248 /** 249 * Get current mode 250 * @return string 251 */ 252 public function getMode() 253 { 254 if(!$this->_mode) return $this->getDefaultMode(); 255 return $this->_mode; 256 } 257 258 /** 259 * Get default mode 260 * @return string 261 */ 262 public function getDefaultMode() 263 { 264 return $this->_default_mode; 265 } 266 267 /** 268 * Get current mode name 269 * 270 * @return string 271 */ 272 public function getModeName() 273 { 274 return strtolower(str_replace('-', '_', $this->getMode())); 275 } 276 277 /** 278 * Reset current mode 279 * @param string $mode 280 * @return e_admin_request 281 */ 282 public function setMode($mode) 283 { 284 $this->_mode = preg_replace('/[^\w]/', '', $mode); 285 $this->setQuery($this->_mode_key, $this->_mode); 286 return $this; 287 } 288 289 /** 290 * Set default mode 291 * @param string $mode 292 * @return e_admin_request 293 */ 294 public function setDefaultMode($mode) 295 { 296 if($mode) $this->_default_mode = $mode; 297 return $this; 298 } 299 300 /** 301 * Set mode key name 302 * @param string $key 303 * @return e_admin_request 304 */ 305 public function setModeKey($key) 306 { 307 $this->_mode_key = $key; 308 return $this; 309 } 310 311 /** 312 * Get current action 313 * @return string 314 */ 315 public function getAction() 316 { 317 if(!$this->_action) return $this->getDefaultAction(); 318 return $this->_action; 319 } 320 321 /** 322 * Get default action 323 * @return string 324 */ 325 public function getDefaultAction() 326 { 327 return $this->_default_action; 328 } 329 330 /** 331 * Get current action name 332 * @return string camelized action 333 */ 334 public function getActionName() 335 { 336 return $this->camelize($this->getAction()); 337 } 338 339 /** 340 * Reset current action 341 * 342 * @param string $action 343 * @return e_admin_request 344 */ 345 public function setAction($action) 346 { 347 $this->_action = preg_replace('/[^\w]/', '', $action); 348 $this->setQuery($this->_action_key, $this->_action); 349 return $this; 350 } 351 352 /** 353 * Set default action 354 * 355 * @param string $action 356 * @return e_admin_request 357 */ 358 public function setDefaultAction($action) 359 { 360 if($action) $this->_default_action = $action; 361 return $this; 362 } 363 364 /** 365 * Set action key name 366 * @param string $key 367 * @return e_admin_request 368 */ 369 public function setActionKey($key) 370 { 371 $this->_action_key = $key; 372 return $this; 373 } 374 375 /** 376 * Get current ID 377 * @return integer 378 */ 379 public function getId() 380 { 381 return $this->_id; 382 } 383 384 /** 385 * Reset current ID 386 * @param string $id 387 * @return e_admin_request 388 */ 389 public function setId($id) 390 { 391 $id = intval($id); 392 $this->_id = $id; 393 $this->setQuery($this->_id_key, $id); 394 return $this; 395 } 396 397 /** 398 * Set id key name 399 * @param string $key 400 * @return e_admin_request 401 */ 402 public function setIdKey($key) 403 { 404 $this->_id_key = $key; 405 return $this; 406 } 407 408 /** 409 * Build query string from current request array 410 * NOTE: changing url separator to & ($encode==true) (thus URL XHTML compliance) works in PHP 5.1.2+ environment 411 * 412 * @param string|array $merge_with [optional] override request values 413 * @param boolean $encode if true & separator will be used, all values will be http encoded, default true 414 * @param string|array $exclude_from_query numeric array/comma separated list of vars to be excluded from current query, true - don't use current query at all 415 * @param boolean $keepSpecial don't exclude special vars as 'mode' and 'action' 416 * @return string url encoded query string 417 */ 418 public function buildQueryString($merge_with = array(), $encode = true, $exclude_from_query = '', $keepSpecial = true) 419 { 420 $ret = $this->getQuery(); 421 422 //special case - exclude all current 423 if(true === $exclude_from_query) 424 { 425 $exclude_from_query = array_keys($ret); 426 } 427 // to array 428 if(is_string($exclude_from_query)) 429 { 430 $exclude_from_query = array_map('trim', explode(',', $exclude_from_query)); 431 } 432 if($exclude_from_query) 433 { 434 foreach ($exclude_from_query as $var) 435 { 436 if($keepSpecial && $var != $this->_action_key && $var != $this->_mode_key) unset($ret[$var]); 437 } 438 } 439 440 if(is_string($merge_with)) 441 { 442 parse_str($merge_with, $merge_with); 443 } 444 $ret = array_merge($ret, (array) $merge_with); 445 $separator = '&'; 446 if($encode) 447 { 448 $separator = '&'; 449 //$ret = array_map('rawurlencode', $ret); 450 } 451 452 $ret = http_build_query($ret, 'numeric_', $separator); 453 if(!$encode) 454 { 455 return rawurldecode($ret); 456 } 457 return $ret; 458 } 459 460 /** 461 * Convert string to CamelCase 462 * 463 * @param string $str 464 * @return string 465 */ 466 public function camelize($str) 467 { 468 return implode('', array_map('ucfirst', explode('-', str_replace('_', '-', $str)))); 469 } 470} 471 472/** 473 * TODO - front response parent, should do all the header.php work 474 */ 475class e_admin_response 476{ 477 /** 478 * Body segments 479 * 480 * @var array 481 */ 482 protected $_body = array(); 483 484 /** 485 * Title segments 486 * 487 * @var unknown_type 488 */ 489 protected $_title = array(); 490 491 /** 492 * e107 meta title 493 * 494 * @var array 495 */ 496 protected $_e_PAGETITLE = array(); 497 498 /** 499 * e107 meta description 500 * 501 * @var array 502 */ 503 protected $_META_DESCRIPTION = array(); 504 505 /** 506 * e107 meta keywords 507 * 508 * @var array 509 */ 510 protected $_META_KEYWORDS = array(); 511 512 /** 513 * Render mods 514 * 515 * @var array 516 */ 517 protected $_render_mod = array(); 518 519 /** 520 * Meta title segment description 521 * 522 * @var string 523 */ 524 protected $_meta_title_separator = ' - '; 525 526 /** 527 * Title segment separator 528 * 529 * @var string 530 */ 531 protected $_title_separator = ' » '; 532 533 /** 534 * Constructor 535 * 536 */ 537 public function __construct() 538 { 539 $this->_render_mod['default'] = 'admin_page'; 540 } 541 542 /** 543 * Set body segments for a namespace 544 * 545 * @param string $content 546 * @param string $namespace segment namesapce 547 * @return e_admin_response 548 */ 549 function setBody($content, $namespace = 'default') 550 { 551 $this->_body[$namespace] = $content; 552 return $this; 553 } 554 555 /** 556 * Append body segment to a namespace 557 * 558 * @param string $content 559 * @param string $namespace segment namesapce 560 * @return e_admin_response 561 */ 562 function appendBody($content, $namespace = 'default') 563 { 564 if(!isset($this->_body[$namespace])) 565 { 566 $this->_body[$namespace] = array(); 567 } 568 $this->_body[$namespace][] = $content; 569 return $this; 570 } 571 572 /** 573 * Prepend body segment to a namespace 574 * 575 * @param string $content 576 * @param string $namespace segment namespace 577 * @return e_admin_response 578 */ 579 function prependBody($content, $namespace = 'default') 580 { 581 if(!isset($this->_body[$namespace])) 582 { 583 $this->_body[$namespace] = array(); 584 } 585 $this->_body[$namespace] = array_merge(array($content), $this->_body[$namespace]); 586 return $this; 587 } 588 589 /** 590 * Get body segments from a namespace 591 * 592 * @param string $namespace segment namesapce 593 * @param boolean $reset reset segment namespace 594 * @param string|boolean $glue if false return array, else return string 595 * @return string|array 596 */ 597 function getBody($namespace = 'default', $reset = false, $glue = '') 598 { 599 $content = vartrue($this->_body[$namespace], array()); 600 if($reset) 601 { 602 $this->_body[$namespace] = array(); 603 } 604 if(is_bool($glue)) 605 { 606 return ($glue ? $content : implode('', $content)); 607 } 608 return implode($glue, $content); 609 } 610 611 /** 612 * Set title segments for a namespace 613 * 614 * @param string $title 615 * @param string $namespace 616 * @return e_admin_response 617 */ 618 function setTitle($title, $namespace = 'default') 619 { 620 $this->_title[$namespace] = array($title); 621 return $this; 622 } 623 624 /** 625 * Append title segment to a namespace 626 * 627 * @param string $title 628 * @param string $namespace segment namesapce 629 * @return e_admin_response 630 */ 631 function appendTitle($title, $namespace = 'default') 632 { 633 if(empty($title)) 634 { 635 return $this; 636 } 637 if(!isset($this->_title[$namespace])) 638 { 639 $this->_title[$namespace] = array(); 640 } 641 $this->_title[$namespace][] = $title; 642 return $this; 643 } 644 645 /** 646 * Prepend title segment to a namespace 647 * 648 * @param string $title 649 * @param string $namespace segment namespace 650 * @return e_admin_response 651 */ 652 function prependTitle($title, $namespace = 'default') 653 { 654 if(empty($title)) 655 { 656 return $this; 657 } 658 if(!isset($this->_title[$namespace])) 659 { 660 $this->_title[$namespace] = array(); 661 } 662 $this->_title[$namespace] = array_merge(array($title), $this->_title[$namespace]); 663 return $this; 664 } 665 666 /** 667 * Get title segments from namespace 668 * 669 * @param string $namespace 670 * @param boolean $reset 671 * @param boolean|string $glue 672 * @return unknown 673 */ 674 function getTitle($namespace = 'default', $reset = false, $glue = ' ') 675 { 676 $content = array(); 677 678 if(isset($this->_title[$namespace]) && is_array($this->_title[$namespace])) 679 { 680 $content = $this->_title[$namespace]; 681 } 682 if($reset) 683 { 684 unset($this->_title[$namespace]); 685 } 686 if(is_bool($glue) || empty($glue)) 687 { 688 return ($glue ? $content : implode($this->_title_separator, $content)); 689 } 690 691 $glue = deftrue('SEP',' - '); // Defined by admin theme. // admin-ui used only by bootstrap. 692 693 return implode($glue, $content); 694 // return $head. implode($glue, $content).$foot; 695 } 696 697 /** 698 * Set render mode for a namespace 699 * 700 * @param string $render_mod 701 * @param string $namespace 702 * @return e_admin_response 703 */ 704 function setRenderMod($render_mod, $namespace = 'default') 705 { 706 $this->_render_mod[$namespace] = $render_mod; 707 return $this; 708 } 709 710 /** 711 * Set render mode for namespace 712 * 713 * @param string $namespace 714 * @return string 715 */ 716 function getRenderMod($namespace = 'default') 717 { 718 return varset($this->_render_mod[$namespace], null); 719 } 720 721 /** 722 * Add meta title, description and keywords segments 723 * 724 * @param string $meta property name 725 * @param string $content meta content 726 * @return e_admin_response 727 */ 728 function addMetaData($meta, $content) 729 { 730 $tp = e107::getParser(); 731 $meta = '_' . $meta; 732 if(isset($this->{$meta}) && !empty($content)) 733 { 734 $this->{$meta}[] = strip_tags($content); 735 } 736 return $this; 737 } 738 739 /** 740 * Add meta title segment 741 * 742 * @param string $title 743 * @return e_admin_response 744 */ 745 function addMetaTitle($title) 746 { 747 $this->addMetaData('e_PAGETITLE', $title); 748 return $this; 749 } 750 751 /** 752 * Add meta description segment 753 * 754 * @param string $description 755 * @return e_admin_response 756 */ 757 function addMetaDescription($description) 758 { 759 $this->addMetaData('META_DESCRIPTION', $description); 760 return $this; 761 } 762 763 /** 764 * Add meta keywords segment 765 * 766 * @param string $keyword 767 * @return e_admin_response 768 */ 769 function addMetaKeywords($keyword) 770 { 771 $this->addMetaData('META_KEYWORDS', $keyword); 772 return $this; 773 } 774 775 /** 776 * Send e107 meta-data 777 * 778 * @return e_admin_response 779 */ 780 function sendMeta() 781 { 782 //HEADERF already included or meta content already sent 783 if(e_AJAX_REQUEST || defined('HEADER_INIT') || defined('e_PAGETITLE')) 784 return $this; 785 786 if(!defined('e_PAGETITLE') && !empty($this->_e_PAGETITLE)) 787 { 788 define('e_PAGETITLE', implode($this->_meta_title_separator, $this->_e_PAGETITLE)); 789 } 790 791 if(!defined('META_DESCRIPTION') && !empty($this->_META_DESCRIPTION)) 792 { 793 define('META_DESCRIPTION', implode(' ', $this->_META_DESCRIPTION)); 794 } 795 if(!defined('META_KEYWORDS') && !empty($this->_META_KEYWORDS)) 796 { 797 define('META_KEYWORDS', implode(', ', $this->_META_KEYWORDS)); 798 } 799 return $this; 800 } 801 802 /** 803 * Add content segment to the header namespace 804 * 805 * @param string $content 806 * @return e_admin_response 807 */ 808 function addHeaderContent($content) 809 { 810 $this->appendBody($content, 'header_content'); 811 return $this; 812 } 813 814 /** 815 * Get page header namespace content segments 816 * 817 * @param boolean $reset 818 * @param boolean $glue 819 * @return string 820 */ 821 function getHeaderContent($reset = true, $glue = "\n\n") 822 { 823 return $this->getBody('header_content', $reset, $glue); 824 } 825 826 /** 827 * Switch to iframe mod 828 * FIXME - implement e_IFRAME to frontend - header_default.php 829 * 830 * @return e_admin_response 831 */ 832 function setIframeMod() 833 { 834 global $HEADER, $FOOTER, $CUSTOMHEADER, $CUSTOMFOOTER; 835 $HEADER = $FOOTER = ''; 836 $CUSTOMHEADER = $CUSTOMFOOTER = array(); 837 //TODO generic $_GET to activate for any page of admin. 838 // New 839 if(!defined('e_IFRAME')) 840 { 841 define('e_IFRAME', true); 842 } 843 return $this; 844 } 845 846 /** 847 * Send Response Output 848 * 849 * @param string $name segment 850 * @param array $options valid keys are: messages|render|meta|return|raw|ajax 851 * @return mixed 852 */ 853 function send($name = 'default', $options = array()) 854 { 855 if(is_string($options)) 856 { 857 parse_str($options, $options); 858 } 859 860 // Merge with all available default options 861 $options = array_merge(array( 862 'messages' => true, 863 'render' => true, 864 'meta' => false, 865 'return' => false, 866 'raw' => false, 867 'ajax' => false 868 ), $options); 869 870 $content = $this->getBody($name, true); 871 $title = $this->getTitle($name, true); 872 $return = $options['return']; 873 874 if($options['ajax'] || e_AJAX_REQUEST) 875 { 876 $type = $options['ajax'] && is_string($options['ajax']) ? $options['ajax'] : ''; 877 $this->getJsHelper()->sendResponse($type); 878 } 879 880 if($options['messages']) 881 { 882 $content = e107::getMessage()->render().$content; 883 } 884 885 if($options['meta']) 886 { 887 $this->sendMeta(); 888 } 889 890 // raw output expected - force return array 891 if($options['raw']) 892 { 893 return array($title, $content, $this->getRenderMod($name)); 894 } 895 896 //render disabled by the controller 897 if(!$this->getRenderMod($name)) 898 { 899 $options['render'] = false; 900 } 901 902 if($options['render']) 903 { 904 return e107::getRender()->tablerender($title, $content, $this->getRenderMod($name), $return); 905 } 906 907 if($return) 908 { 909 return $content; 910 } 911 912 print($content); 913 return ''; 914 } 915 916 /** 917 * Get JS Helper instance 918 * 919 * @return e_jshelper 920 */ 921 public function getJsHelper() 922 { 923 return e107::getSingleton('e_jshelper', true, 'admin_response'); 924 } 925} 926 927/** 928 * TODO - request related code should be moved to core 929 * request handler 930 */ 931class e_admin_dispatcher 932{ 933 /** 934 * @var e_admin_request 935 */ 936 protected $_request = null; 937 938 /** 939 * @var e_admin_response 940 */ 941 protected $_response = null; 942 943 /** 944 * @var e_admin_controller 945 */ 946 protected $_current_controller = null; 947 948 /** 949 * Required (set by child class). 950 * Controller map array in format 951 * 'MODE' => array('controller' =>'CONTROLLER_CLASS_NAME'[, 'path' => 'CONTROLLER SCRIPT PATH', 'ui' => extend of 'comments_admin_form_ui', 'uipath' => 'path/to/ui/']); 952 * 953 * @var array 954 */ 955 protected $modes = array(); 956 957 /** 958 * Optional - access restrictions per action 959 * Access array in format (similar to adminMenu) 960 * 'MODE/ACTION' => e_UC_* (userclass constant, or custom userclass ID if dynamically set) 961 * 962 * @var array 963 */ 964 protected $access = array(); 965 966 /** 967 * Optional - generic entry point access restriction (via getperms()) 968 * Value of this for plugins would be always 'P'. 969 * When an array is detected, route mode/action = admin perms is used. (similar to $access) 970 * More detailed access control is granted with $access and $modes[MODE]['perm'] or $modes[MODE]['userclass'] settings 971 * 972 * @var string|array 973 */ 974 protected $perm; 975 976 /** 977 * @var string 978 */ 979 protected $defaultMode = ''; 980 981 /** 982 * @var string 983 */ 984 protected $defaultAction = ''; 985 986 /** 987 * Optional - map 'mode/action' pair to 'modeAlias/actionAlias' 988 * @var string 989 */ 990 protected $adminMenuAliases = array(); 991 992 /** 993 * Optional (set by child class). 994 * Required for admin menu render 995 * Format: 'mode/action' => array('caption' => 'Link title'[, 'perm' => '0', 'url' => '{e_PLUGIN}plugname/admin_config.php'], ...); 996 * Note that 'perm' and 'userclass' restrictions are inherited from the $modes, $access and $perm, so you don't have to set that vars if 997 * you don't need any additional 'visual' control. 998 * All valid key-value pair (see e107::getNav()->admin function) are accepted. 999 * @var array 1000 */ 1001 protected $adminMenu = array(); 1002 1003 1004 protected $adminMenuIcon = null; 1005 /** 1006 * Optional (set by child class). 1007 * Page titles for pages not in adminMenu (e.g. main/edit) 1008 * Format array(mod/action => Page Title) 1009 * @var string 1010 */ 1011 protected $pageTitles = array( 1012 'main/edit' => LAN_MANAGE, 1013 ); 1014 1015 /** 1016 * Optional (set by child class). 1017 * @var string 1018 */ 1019 protected $menuTitle = 'Menu'; 1020 1021 /** 1022 * @var string 1023 */ 1024 protected $pluginTitle = ''; 1025 1026 /** 1027 * Constructor 1028 * 1029 * @param string|array|e_admin_request $request [optional] 1030 * @param e_admin_response $response 1031 */ 1032 public function __construct($auto_observe = true, $request = null, $response = null) 1033 { 1034 // we let know some admin routines we are in UI mod - related with some legacy checks and fixes 1035 if(!defined('e_ADMIN_UI')) 1036 { 1037 define('e_ADMIN_UI', true); 1038 } 1039 1040 if(!empty($_GET['iframe'])) 1041 { 1042 define('e_IFRAME', true); 1043 } 1044 1045 require_once(e_ADMIN.'boot.php'); 1046 1047 if(null === $request || !is_object($request)) 1048 { 1049 $request = new e_admin_request($request); 1050 } 1051 1052 if(null === $response) 1053 { 1054 $response = new e_admin_response(); 1055 } 1056 1057 $this->setRequest($request)->setResponse($response)->init(); 1058 1059 if(!$this->defaultMode || !$this->defaultAction) 1060 { 1061 $this->setDefaults(); 1062 } 1063 1064 1065 1066 // current user does not have access to default route, so find a new one. 1067 if(!$hasAccess = $this->hasRouteAccess($this->defaultMode.'/'.$this->defaultAction)) 1068 { 1069 if($newRoute = $this->getApprovedAccessRoute()) 1070 { 1071 list($this->defaultMode,$this->defaultAction) = explode('/',$newRoute); 1072 } 1073 } 1074 1075 1076 $request->setDefaultMode($this->defaultMode)->setDefaultAction($this->defaultAction); 1077 1078 // register itself 1079 e107::setRegistry('admin/ui/dispatcher', $this); 1080 1081 // permissions and restrictions 1082 $this->checkAccess(); 1083 1084 if($auto_observe) 1085 { 1086 $this->runObservers(true); 1087 } 1088 } 1089 1090 /** 1091 * User defined constructor - called before _initController() method 1092 * @return e_admin_dispatcher 1093 */ 1094 public function init() 1095 { 1096 } 1097 1098 public function checkAccess() 1099 { 1100 $request = $this->getRequest(); 1101 $currentMode = $request->getMode(); 1102 1103 // access based on mode setting - general controller access 1104 if(!$this->hasModeAccess($currentMode)) 1105 { 1106 $request->setAction('e403'); 1107 e107::getMessage()->addError(LAN_NO_PERMISSIONS) 1108 ->addDebug('Mode access restriction triggered.'); 1109 return false; 1110 } 1111 1112 // access based on $access settings - access per action 1113 $currentAction = $request->getAction(); 1114 $route = $currentMode.'/'.$currentAction; 1115 1116 1117 1118 if(!$this->hasRouteAccess($route)) 1119 { 1120 $request->setAction('e403'); 1121 e107::getMessage()->addError(LAN_NO_PERMISSIONS) 1122 ->addDebug('Route access restriction triggered:'.$route); 1123 return false; 1124 } 1125 1126 return true; 1127 } 1128 1129 public function hasModeAccess($mode) 1130 { 1131 // mode userclass (former check_class()) 1132 if(isset($this->modes[$mode]['userclass']) && !e107::getUser()->checkClass($this->modes[$mode]['userclass'], false)) 1133 { 1134 return false; 1135 } 1136 // mode admin permission (former getperms()) 1137 if(isset($this->modes[$mode]['perm']) && !e107::getUser()->checkAdminPerms($this->modes[$mode]['perm'])) 1138 { 1139 return false; 1140 } 1141 1142 // generic dispatcher admin permission (former getperms()) 1143 if(null !== $this->perm && is_string($this->perm) && !e107::getUser()->checkAdminPerms($this->perm)) 1144 { 1145 return false; 1146 } 1147 1148 return true; 1149 } 1150 1151 public function hasRouteAccess($route) 1152 { 1153 if(isset($this->access[$route]) && !e107::getUser()->checkClass($this->access[$route], false)) 1154 { 1155 return false; 1156 } 1157 1158 if(is_array($this->perm) && isset($this->perm[$route]) && !e107::getUser()->checkAdminPerms($this->perm[$route])) 1159 { 1160 return false; 1161 } 1162 1163 1164 return true; 1165 } 1166 1167 /** 1168 * Retrieve missing default action/mode 1169 * @return e_admin_dispatcher 1170 */ 1171 public function setDefaults() 1172 { 1173 // try Admin menu first 1174 if($this->adminMenu) 1175 { 1176 reset($this->adminMenu); 1177 list($mode, $action) = explode('/', key($this->adminMenu), 3); 1178 } 1179 else 1180 { 1181 reset($this->modes); 1182 $mode = key($this->modes); 1183 $action = $this->modes[$mode]['index']; 1184 } 1185 1186 1187 1188 1189 if(!$this->defaultMode) $this->defaultMode = $mode; 1190 if(!$this->defaultAction) $this->defaultAction = $action; 1191 1192 return $this; 1193 } 1194 1195 /** 1196 * Search through access for an approved route. 1197 * Returns false if no approved route found. 1198 * 1199 * @return string|bool 1200 */ 1201 private function getApprovedAccessRoute() 1202 { 1203 if(empty($this->access)) 1204 { 1205 return false; 1206 } 1207 1208 foreach($this->access as $route=>$uclass) 1209 { 1210 if(check_class($uclass)) 1211 { 1212 return $route; 1213 } 1214 } 1215 1216 return false; 1217 } 1218 1219 /** 1220 * Get admin menu array 1221 * @return array 1222 */ 1223 public function getMenuData() 1224 { 1225 return $this->adminMenu; 1226 } 1227 1228 /** 1229 * Get admin menu array 1230 * @return array 1231 */ 1232 public function getPageTitles() 1233 { 1234 return $this->pageTitles; 1235 } 1236 1237 /** 1238 * Get admin menu array 1239 * @return array 1240 */ 1241 public function getMenuAliases() 1242 { 1243 return $this->adminMenuAliases; 1244 } 1245 1246 /** 1247 * Get request object 1248 * @return e_admin_request 1249 */ 1250 public function getRequest() 1251 { 1252 return $this->_request; 1253 } 1254 1255 /** 1256 * Set request object 1257 * @param e_admin_request $request 1258 * @return e_admin_dispatcher 1259 */ 1260 public function setRequest($request) 1261 { 1262 $this->_request = $request; 1263 return $this; 1264 } 1265 1266 /** 1267 * Get response object 1268 * @return e_admin_response 1269 */ 1270 public function getResponse() 1271 { 1272 return $this->_response; 1273 } 1274 1275 /** 1276 * Set response object 1277 * @param e_admin_response $response 1278 * @return e_admin_dispatcher 1279 */ 1280 public function setResponse($response) 1281 { 1282 $this->_response = $response; 1283 return $this; 1284 } 1285 1286 /** 1287 * Dispatch & render all 1288 * 1289 * @param boolean $run_header see runObservers() 1290 * @param boolean $return see runPage() 1291 * @return string|array current admin page body 1292 */ 1293 public function run($run_header = true, $return = 'render') 1294 { 1295 return $this->runObservers()->runPage($return); 1296 } 1297 1298 /** 1299 * Run observers/headers only, should be called before header.php call 1300 * 1301 * @return e_admin_dispatcher 1302 */ 1303 public function runObservers($run_header = true) 1304 { 1305 //search for $actionName.'Observer' method. Additional $actionName.$triggerName.'Trigger' methods will be called as well 1306 $this->getController()->dispatchObserver(); 1307 1308 //search for $actionName.'Header' method, js manager should be used inside for sending JS to the page, 1309 // meta information should be created there as well 1310 if($run_header) 1311 { 1312 $this->getController()->dispatchHeader(); 1313 1314 } 1315 return $this; 1316 } 1317 1318 /** 1319 * Run page action. 1320 * If return type is array, it should contain allowed response options (see e_admin_response::send()) 1321 * Available return type string values: 1322 * - render_return: return rendered content ( see e107::getRender()->tablerender()), add system messages, send meta information 1323 * - render: outputs rendered content ( see e107::getRender()->tablerender()), add system messages 1324 * - response: return response object 1325 * - raw: return array(title, content, render mode) 1326 * - ajax: force ajax output (and exit) 1327 * 1328 * @param string|array $return_type expected string values: render|render_out|response|raw|ajax[_text|_json|_xml] 1329 * @return mixed 1330 */ 1331 public function runPage($return_type = 'render') 1332 { 1333 $response = $this->getController()->dispatchPage(); 1334 if(is_array($return_type)) 1335 { 1336 return $response->send('default', $return_type); 1337 } 1338 switch($return_type) 1339 { 1340 case 'render_return': 1341 $options = array( 1342 'messages' => true, 1343 'render' => true, 1344 'meta' => true, 1345 'return' => true, 1346 'raw' => false 1347 ); 1348 break; 1349 1350 case 'raw': 1351 $options = array( 1352 'messages' => false, 1353 'render' => false, 1354 'meta' => false, 1355 'return' => true, 1356 'raw' => true 1357 ); 1358 break; 1359 1360 case 'ajax': 1361 case 'ajax_text': 1362 case 'ajax_xml'; 1363 case 'ajax_json'; 1364 $options = array( 1365 'messages' => false, 1366 'render' => false, 1367 'meta' => false, 1368 'return' => false, 1369 'raw' => false, 1370 'ajax' => str_replace(array('ajax_', 'ajax'), array('', 'text'), $return_type) 1371 ); 1372 break; 1373 1374 case 'response': 1375 return $response; 1376 break; 1377 1378 case 'render': 1379 default: 1380 $options = array( 1381 'messages' => true, 1382 'render' => true, 1383 'meta' => false, 1384 'return' => false, 1385 'raw' => false 1386 ); 1387 break; 1388 } 1389 return $response->send('default', $options); 1390 } 1391 1392 1393 /** 1394 * Get perms 1395 * @return array|string 1396 */ 1397 public function getPerm() 1398 { 1399 return $this->perm; 1400 } 1401 1402 /** 1403 * Proxy method 1404 * 1405 * @return string 1406 */ 1407 public function getHeader() 1408 { 1409 return $this->getController()->getHeader(); 1410 } 1411 1412 /** 1413 * Get current controller object 1414 * @return e_admin_controller 1415 */ 1416 public function getController() 1417 { 1418 if(null === $this->_current_controller) 1419 { 1420 $this->_initController(); 1421 } 1422 return $this->_current_controller; 1423 } 1424 1425 /** 1426 * Try to init Controller from request using current controller map 1427 * 1428 * @return e_admin_dispatcher 1429 */ 1430 protected function _initController() 1431 { 1432 $request = $this->getRequest(); 1433 $response = $this->getResponse(); 1434 if(isset($this->modes[$request->getModeName()]) && isset($this->modes[$request->getModeName()]['controller'])) 1435 { 1436 $class_name = $this->modes[$request->getModeName()]['controller']; 1437 $class_path = vartrue($this->modes[$request->getModeName()]['path']); 1438 1439 if($class_path) 1440 { 1441 require_once(e107::getParser()->replaceConstants($class_path)); 1442 } 1443 if($class_name && class_exists($class_name))//NOTE: autoload in the play 1444 { 1445 $this->_current_controller = new $class_name($request, $response); 1446 //give access to current request object, user defined init 1447 $this->_current_controller->setRequest($this->getRequest())->init(); 1448 } 1449 // Known controller (found in e_admin_dispatcher::$modes), class not found exception 1450 else 1451 { 1452 // TODO - admin log 1453 // get default controller 1454 $this->_current_controller = $this->getDefaultController(); 1455 // add messages 1456 e107::getMessage()->add('Can\'t find class <strong>"'.($class_name ? $class_name : 'n/a').'"</strong> for controller <strong>"'.ucfirst($request->getModeName()).'"</strong>', E_MESSAGE_ERROR) 1457 ->add('Requested: '.e_REQUEST_SELF.'?'.$request->buildQueryString(), E_MESSAGE_DEBUG); 1458 // 1459 $request->setMode($this->getDefaultControllerName())->setAction('e404'); 1460 $this->_current_controller->setRequest($request)->init(); 1461 } 1462 1463 if(vartrue($this->modes[$request->getModeName()]['ui'])) 1464 { 1465 $class_name = $this->modes[$request->getModeName()]['ui']; 1466 $class_path = vartrue($this->modes[$request->getModeName()]['uipath']); 1467 if($class_path) 1468 { 1469 require_once(e107::getParser()->replaceConstants($class_path)); 1470 } 1471 if(class_exists($class_name))//NOTE: autoload in the play 1472 { 1473 $this->_current_controller->setParam('ui', new $class_name($this->_current_controller)); 1474 } 1475 } 1476 $this->_current_controller->setParam('modes', $this->modes); 1477 1478 } 1479 // Not known controller (not found in e_admin_dispatcher::$modes) exception 1480 else 1481 { 1482 // TODO - admin log 1483 $this->_current_controller = $this->getDefaultController(); 1484 // add messages 1485 e107::getMessage()->add('Can\'t find class for controller <strong>"'.ucfirst($request->getModeName()).'"</strong>', E_MESSAGE_ERROR) 1486 ->add('Requested: '.e_REQUEST_SELF.'?'.$request->buildQueryString(), E_MESSAGE_DEBUG); 1487 // go to not found page 1488 $request->setMode($this->getDefaultControllerName())->setAction('e404'); 1489 $this->_current_controller->setRequest($request)->init(); 1490 } 1491 1492 return $this; 1493 } 1494 1495 /** 1496 * Default controller object - needed if controller not found 1497 * @return e_admin_controller 1498 */ 1499 public function getDefaultController() 1500 { 1501 $class_name = $this->getDefaultControllerName(); 1502 return new $class_name($this->getRequest(), $this->getResponse()); 1503 } 1504 1505 /** 1506 * Default controller name - needed if controller not found 1507 * @return string name of controller 1508 */ 1509 public function getDefaultControllerName() 1510 { 1511 return 'e_admin_controller'; 1512 } 1513 1514 /** 1515 * Generic Admin Menu Generator 1516 * @return string 1517 */ 1518 function renderMenu() 1519 { 1520 1521 $tp = e107::getParser(); 1522 $var = array(); 1523 $selected = false; 1524 1525 foreach($this->adminMenu as $key => $val) 1526 { 1527 1528 if(isset($val['perm']) && $val['perm']!=='' && !getperms($val['perm'])) 1529 { 1530 continue; 1531 } 1532 1533 $tmp = explode('/', trim($key, '/'), 3); 1534 1535 // sync with mode/route access 1536 if(!$this->hasModeAccess($tmp[0]) || !$this->hasRouteAccess($tmp[0].'/'.varset($tmp[1]))) 1537 { 1538 continue; 1539 } 1540 1541 // custom 'selected' check 1542 if(isset($val['selected']) && $val['selected']) $selected = $val['selected'] === true ? $key : $val['selected']; 1543 1544 foreach ($val as $k=>$v) 1545 { 1546 switch($k) 1547 { 1548 case 'caption': 1549 $k2 = 'text'; 1550 $v = defset($v, $v); 1551 1552 break; 1553 1554 case 'url': 1555 $k2 = 'link'; 1556 $qry = (isset($val['query'])) ? $val['query'] : '?mode='.$tmp[0].'&action='.$tmp[1]; 1557 $v = $tp->replaceConstants($v, 'abs').$qry; 1558 break; 1559 1560 case 'uri': 1561 $k2 = 'link'; 1562 $v = $tp->replaceConstants($v, 'abs'); 1563 1564 if(!empty($v) && (e_REQUEST_URI === $v)) 1565 { 1566 $selected = $key; 1567 } 1568 1569 break; 1570 1571 1572 case 'badge': // array('value'=> int, 'type'=>'warning'); 1573 $k2 = 'badge'; 1574 $v = (array) $v; 1575 break; 1576 1577 default: 1578 $k2 = $k; 1579 1580 break; 1581 } 1582 1583 1584 1585 // Access check done above 1586 // if($val['perm']!= null) // check perms 1587 // { 1588 // if(getperms($val['perm'])) 1589 // { 1590 // $var[$key][$k2] = $v; 1591 // } 1592 // } 1593 // else 1594 { 1595 $var[$key][$k2] = $v; 1596 1597 } 1598 1599 } 1600 1601 1602 1603 // TODO slide down menu options? 1604 if(!vartrue($var[$key]['link'])) 1605 { 1606 $var[$key]['link'] = e_REQUEST_SELF.'?mode='.$tmp[0].'&action='.$tmp[1]; // FIXME - URL based on $modes, remove url key 1607 } 1608 1609 1610 if(varset($val['tab'])) 1611 { 1612 $var[$key]['link'] .= "&tab=".$val['tab']; 1613 } 1614 1615 /*$var[$key]['text'] = $val['caption']; 1616 $var[$key]['link'] = (vartrue($val['url']) ? $tp->replaceConstants($val['url'], 'abs') : e_SELF).'?mode='.$tmp[0].'&action='.$tmp[1]; 1617 $var[$key]['perm'] = $val['perm']; */ 1618 if(!empty($val['modal'])) 1619 { 1620 $var[$key]['link_class'] = ' e-modal'; 1621 if(!empty($val['modal-caption'])) 1622 { 1623 $var[$key]['link_data'] = array('data-modal-caption' => $val['modal-caption']); 1624 } 1625 1626 } 1627 1628 } 1629 1630 1631 if(empty($var)) return ''; 1632 1633 $request = $this->getRequest(); 1634 if(!$selected) $selected = $request->getMode().'/'.$request->getAction(); 1635 $selected = vartrue($this->adminMenuAliases[$selected], $selected); 1636 1637 $icon = ''; 1638 1639 if(!empty($this->adminMenuIcon)) 1640 { 1641 $icon = e107::getParser()->toIcon($this->adminMenuIcon); 1642 } 1643 elseif(deftrue('e_CURRENT_PLUGIN')) 1644 { 1645 $icon = e107::getPlug()->load(e_CURRENT_PLUGIN)->getIcon(24); 1646 } 1647 1648 return e107::getNav()->admin($icon."<span>".$this->menuTitle."</span>", $selected, $var); 1649 } 1650 1651 1652 /** 1653 * Render Help Text in <ul> format. XXX TODO 1654 */ 1655 function renderHelp() 1656 { 1657 1658 1659 1660 } 1661 1662 1663 /** 1664 * Check for table issues and warn the user. XXX TODO 1665 * ie. user is using French interface but no french tables found for the current DB tables. 1666 */ 1667 function renderWarnings() 1668 { 1669 1670 1671 1672 1673 } 1674 1675 1676} 1677 1678class e_admin_controller 1679{ 1680 /** 1681 * @var e_admin_request 1682 */ 1683 protected $_request; 1684 1685 /** 1686 * @var e_admin_response 1687 */ 1688 protected $_response; 1689 1690 /** 1691 * @var array User defined parameters 1692 */ 1693 protected $_params = array(); 1694 1695 /** 1696 * @var string default action name 1697 */ 1698 protected $_default_action = 'index'; 1699 1700 1701 /** 1702 * @var string default trigger action. 1703 */ 1704 protected $_default_trigger = 'auto'; 1705 1706 /** 1707 * List (numerical array) of only allowed for this controller actions 1708 * Useful to grant access for certain pre-defined actions only 1709 * XXX - we may move this in dispatcher (or even having it also there), still searching the most 'friendly' way 1710 * @var array 1711 */ 1712 protected $allow = array(); 1713 1714 /** 1715 * List (numerical array) of only disallowed for this controller actions 1716 * Useful to restrict access for certain pre-defined actions only 1717 * XXX - we may move this in dispatcher (or even having it also there), still searching the most 'friendly' way 1718 * @var array 1719 */ 1720 protected $disallow = array(); 1721 1722 /** 1723 * Constructor 1724 * @param e_admin_request $request [optional] 1725 */ 1726 public function __construct($request, $response, $params = array()) 1727 { 1728 $this->_params = array_merge(array('enable_triggers' => false), $params); 1729 $this->setRequest($request) 1730 ->setResponse($response) 1731 ->setParams($params); 1732 1733 $this->checkAccess(); 1734 1735 $this->_log(); // clear the log (when debug is enabled) 1736 1737 } 1738 1739 /** 1740 * Check against allowed/disallowed actions 1741 * FIXME check plugin admin access (check_class(P)), confirm e-token is verified 1742 */ 1743 public function checkAccess() 1744 { 1745 $request = $this->getRequest(); 1746 $currentAction = $request->getAction(); 1747 1748 // access based on mode setting - general controller access 1749 if(!empty($this->disallow) && in_array($currentAction, $this->disallow)) 1750 { 1751 $request->setAction('e403'); 1752 e107::getMessage()->addError(LAN_NO_PERMISSIONS) 1753 ->addDebug('Controller action disallowed restriction triggered.'); 1754 return false; 1755 } 1756 1757 // access based on $access settings - access per action 1758 if(!empty($this->allow) && !in_array($currentAction, $this->allow)) 1759 { 1760 $request->setAction('e403'); 1761 e107::getMessage()->addError(LAN_NO_PERMISSIONS) 1762 ->addDebug('Controller action not in allowed list restriction triggered.'); 1763 return false; 1764 } 1765 return true; 1766 } 1767 1768 /** 1769 * User defined init 1770 * Called before dispatch routine 1771 */ 1772 public function init() 1773 { 1774 } 1775 1776 /** 1777 * Get controller parameter 1778 * Currently used core parameters: 1779 * - enable_triggers: don't use it direct, see {@link setTriggersEnabled()} 1780 * - modes - see dispatcher::$modes 1781 * - ajax_response - text|xml|json - default is 'text'; this should be set by the action method 1782 * - TODO - more parameters/add missing to this list 1783 * 1784 * @param string $key [optional] if null - get whole array 1785 * @param mixed $default [optional] 1786 * @return mixed 1787 */ 1788 public function getParam($key = null, $default = null) 1789 { 1790 if(null === $key) 1791 { 1792 return $this->_params; 1793 } 1794 return (isset($this->_params[$key]) ? $this->_params[$key] : $default); 1795 } 1796 1797 /** 1798 * Set parameter 1799 * @param string $key 1800 * @param mixed $value 1801 * @return e_admin_controller 1802 */ 1803 public function setParam($key, $value) 1804 { 1805 if(null === $value) 1806 { 1807 unset($this->_params[$key]); 1808 return $this; 1809 } 1810 $this->_params[$key] = $value; 1811 return $this; 1812 } 1813 1814 /** 1815 * Merge passed parameter array with current parameters 1816 * @param array $params 1817 * @return e_admin_controller 1818 */ 1819 public function setParams($params) 1820 { 1821 $this->_params = array_merge($this->_params, $params); 1822 return $this; 1823 } 1824 1825 /** 1826 * Reset parameter array 1827 * @param array $params 1828 * @return e_admin_controller 1829 */ 1830 public function resetParams($params) 1831 { 1832 $this->_params = $params; 1833 return $this; 1834 } 1835 1836 /** 1837 * Get current request object 1838 * @return e_admin_request 1839 */ 1840 public function getRequest() 1841 { 1842 return $this->_request; 1843 } 1844 1845 /** 1846 * Set current request object 1847 * @param e_admin_request $request 1848 * @return e_admin_controller 1849 */ 1850 public function setRequest($request) 1851 { 1852 $this->_request = $request; 1853 return $this; 1854 } 1855 1856 /** 1857 * Get current response object 1858 * @return e_admin_response 1859 */ 1860 public function getResponse() 1861 { 1862 return $this->_response; 1863 } 1864 1865 /** 1866 * Set current response object 1867 * @param e_admin_response $response 1868 * @return e_admin_controller 1869 */ 1870 public function setResponse($response) 1871 { 1872 $this->_response = $response; 1873 return $this; 1874 } 1875 1876 /** 1877 * Get current dispatcher object 1878 * @return e_admin_dispatcher 1879 */ 1880 public function getDispatcher() 1881 { 1882 return e107::getRegistry('admin/ui/dispatcher'); 1883 } 1884 1885 /** 1886 * Request proxy method 1887 * @param string $key [optional] 1888 * @param mixed $default [optional] 1889 * @return mixed 1890 */ 1891 public function getQuery($key = null, $default = null) 1892 { 1893 return $this->getRequest()->getQuery($key, $default); 1894 } 1895 1896 /** 1897 * Request proxy method 1898 * @param string|array $key 1899 * @param mixed $value [optional] 1900 * @return e_admin_controller 1901 */ 1902 public function setQuery($key, $value = null) 1903 { 1904 $this->getRequest()->setQuery($key, $value); 1905 return $this; 1906 } 1907 1908 /** 1909 * Request proxy method 1910 * @param string $key [optional] 1911 * @param mixed $default [optional] 1912 * @return mixed 1913 */ 1914 public function getPosted($key = null, $default = null) 1915 { 1916 return $this->getRequest()->getPosted($key, $default); 1917 } 1918 1919 /** 1920 * Request proxy method 1921 * @param string $key 1922 * @param mixed $value [optional] 1923 * @return e_admin_controller 1924 */ 1925 public function setPosted($key, $value = null) 1926 { 1927 $this->getRequest()->setPosted($key, $value); 1928 return $this; 1929 } 1930 1931 /** 1932 * Add page title, response proxy method 1933 * 1934 * @param string $title if boolean true - current menu caption will be used 1935 * @param boolean $meta add to meta as well 1936 * @return object e_admin_controller 1937 */ 1938 public function addTitle($title = true, $meta = true) 1939 { 1940 1941 1942 if(true === $title) 1943 { 1944 $_dispatcher = $this->getDispatcher(); 1945 $data = $_dispatcher->getPageTitles(); 1946 $search = $this->getMode().'/'.$this->getAction(); 1947 1948 1949 1950 if(isset($data[$search])) 1951 { 1952 $res['caption'] = $data[$search]; 1953 } 1954 else 1955 { 1956 1957 1958 $data = $_dispatcher->getMenuData(); 1959 1960 if(isset($data[$search])) 1961 { 1962 $res = $data[$search]; 1963 } 1964 else 1965 { 1966 // check for an alias match. 1967 $d = $_dispatcher->getMenuAliases(); 1968 if(isset($d[$search])) 1969 { 1970 $search = $d[$search]; 1971 $res = $data[$search]; 1972 1973 } 1974 else 1975 { 1976 return $this; 1977 } 1978 // var_dump($d); 1979 // var_dump("Couldnt find: ".$search); 1980 1981 } 1982 } 1983 $title = $res['caption']; 1984 1985 1986 } 1987 1988 // echo "<h3>".__METHOD__." - ".$title."</h3>"; 1989 1990 // print_a($title); 1991 $this->getResponse()->appendTitle($title); 1992 if($meta) $this->addMetaTitle($title); 1993 1994 return $this; 1995 } 1996 1997 /** 1998 * Add page meta title, response proxy method. 1999 * Should be called before header.php 2000 * 2001 * @param string $title 2002 * @return e_admin_controller 2003 */ 2004 public function addMetaTitle($title=null) 2005 { 2006 if($title === null) 2007 { 2008 return $this; 2009 } 2010 2011 $this->getResponse()->addMetaTitle($title); 2012 return $this; 2013 } 2014 2015 /** 2016 * Add header content, response proxy method 2017 * Should be called before header.php 2018 * 2019 * @param string $content 2020 * @return e_admin_controller 2021 */ 2022 public function addHeader($content=null) 2023 { 2024 if($content === null) 2025 { 2026 return $this; 2027 } 2028 2029 $this->getResponse()->addHeaderContent(vartrue($content)); 2030 return $this; 2031 } 2032 2033 /** 2034 * Get header content, response proxy method 2035 * 2036 * @return string 2037 */ 2038 public function getHeader() 2039 { 2040 return $this->getResponse()->getHeaderContent(); 2041 } 2042 2043 /** 2044 * Get current mode, response proxy method 2045 * @return string 2046 */ 2047 public function getMode() 2048 { 2049 return $this->getRequest()->getMode(); 2050 } 2051 2052 /** 2053 * Get current actin, response proxy method 2054 * @return string 2055 */ 2056 public function getAction() 2057 { 2058 return $this->getRequest()->getAction(); 2059 } 2060 2061 /** 2062 * Get current ID, response proxy method 2063 * @return string 2064 */ 2065 public function getId() 2066 { 2067 return $this->getRequest()->getId(); 2068 } 2069 2070 /** 2071 * Get response owned JS Helper instance, response proxy method 2072 * 2073 * @return e_jshelper 2074 */ 2075 public function getJsHelper() 2076 { 2077 return $this->getResponse()->getJsHelper(); 2078 } 2079 2080 protected function _preDispatch($action = '') 2081 { 2082 if(!$action) $action = $this->getRequest()->getActionName(); 2083 $method = $this->toMethodName($action, 'page'); 2084 if(!method_exists($this, $method)) 2085 { 2086 $this->_log("Skipping ".$method."() (not found)"); 2087 $this->getRequest()->setAction($this->getDefaultAction()); 2088 } 2089 2090 // switch to 404 if needed 2091 $method = $this->toMethodName($this->getRequest()->getActionName(), 'page'); 2092 if(!method_exists($this, $method)) 2093 { 2094 $this->_log("Skipping ".$method."() (not found)"); 2095 $this->getRequest()->setAction('e404'); 2096 $message = e107::getParser()->lanVars(LAN_UI_404_METHOD_ERROR, $method, true); 2097 e107::getMessage()->add($message, E_MESSAGE_ERROR); 2098 } 2099 } 2100 2101 /** 2102 * Log Controller when e_DEBUG is active. 2103 * @param string|null $message 2104 * @return null 2105 */ 2106 protected function _log($message=null) 2107 { 2108 if(!deftrue('e_DEBUG')) 2109 { 2110 return null; 2111 } 2112 2113 if($message === null) // clear the log. 2114 { 2115 file_put_contents(e_LOG."adminUI.log", ''); 2116 return null; 2117 } 2118 2119 $date = (!empty($message)) ? date('c') : ''; 2120 2121 file_put_contents(e_LOG."adminUI.log",$date."\t".$message."\n",FILE_APPEND); 2122 2123 } 2124 2125 2126 /** 2127 * Dispatch observer, check for triggers 2128 * 2129 * @param string $action [optional] 2130 * @return e_admin_controller 2131 */ 2132 public function dispatchObserver($action = null) 2133 { 2134 $request = $this->getRequest(); 2135 if(null === $request) 2136 { 2137 $request = new e_admin_request(); 2138 $this->setRequest($request); 2139 } 2140 2141 $this->_preDispatch($action); 2142 if(null === $action) 2143 { 2144 $action = $request->getActionName(); 2145 } 2146 2147 // check for observer 2148 $actionObserverName = $this->toMethodName($action, 'observer', e_AJAX_REQUEST); 2149 if(method_exists($this, $actionObserverName)) 2150 { 2151 $this->_log("Executing ".$actionObserverName."()"); 2152 $this->$actionObserverName(); 2153 } 2154 else 2155 { 2156 $this->_log("Skipping ".$actionObserverName."() (not found)"); 2157 } 2158 2159 // check for triggers, not available in Ajax mode 2160 if(!e_AJAX_REQUEST && $this->triggersEnabled()) 2161 { 2162 $posted = $request->getPosted(); 2163 foreach ($posted as $key => $value) 2164 { 2165 if(strpos($key, 'etrigger_') === 0) 2166 { 2167 $actionTriggerName = $this->toMethodName($action.$request->camelize(substr($key, 9)), 'trigger', false); 2168 if(method_exists($this, $actionTriggerName)) 2169 { 2170 $this->$actionTriggerName($value); 2171 } 2172 //Check if triggers are still enabled 2173 if(!$this->triggersEnabled()) 2174 { 2175 break; 2176 } 2177 } 2178 } 2179 } 2180 2181 return $this; 2182 } 2183 2184 /** 2185 * Dispatch header, not allowed in Ajax mode 2186 * @param string $action [optional] 2187 * @return e_admin_controller 2188 */ 2189 public function dispatchHeader($action = null) 2190 { 2191 // not available in Ajax mode 2192 if(e_AJAX_REQUEST) 2193 { 2194 return $this; 2195 } 2196 2197 $request = $this->getRequest(); 2198 if(null === $request) 2199 { 2200 $request = new e_admin_request(); 2201 $this->setRequest($request); 2202 } 2203 2204 $this->_preDispatch($action); 2205 if(null === $action) 2206 { 2207 $action = $request->getActionName(); 2208 } 2209 2210 // check for observer 2211 $actionHeaderName = $this->toMethodName($action, 'header', false); 2212 if(method_exists($this, $actionHeaderName)) 2213 { 2214 $this->$actionHeaderName(); 2215 } 2216 2217 //send meta data 2218 $this->getResponse()->sendMeta(); 2219 return $this; 2220 } 2221 2222 /** 2223 * Dispatch controller action 2224 * 2225 * @param string $action [optional] 2226 * @return e_admin_response 2227 */ 2228 public function dispatchPage($action = null) 2229 { 2230 $request = $this->getRequest(); 2231 if(null === $request) 2232 { 2233 $request = new e_admin_request(); 2234 $this->setRequest($request); 2235 } 2236 $response = $this->getResponse(); 2237 // print_a($response); 2238 $this->_preDispatch($action); 2239 2240 if(null === $action) 2241 { 2242 $action = $request->getActionName(); 2243 } 2244 2245 // check for observer 2246 $actionName = $this->toMethodName($action, 'page'); 2247 $ret = ''; 2248 if(!method_exists($this, $actionName)) // pre dispatch already switched to default action/not found page if needed 2249 { 2250 $this->_log("Skipping ".$actionName."() (not found)"); 2251 e107::getMessage()->add('Action '.$actionName.' no found!', E_MESSAGE_ERROR); 2252 return $response; 2253 } 2254 else 2255 { 2256 $this->_log("Executing ".$actionName."()"); 2257 } 2258 2259 if($action != 'Prefs' && $action != 'Create' && $action !='Edit' && $action != 'List') // Custom Page method in use, so add the title. 2260 { 2261 $this->addTitle(); 2262 } 2263 2264 2265 // e107::getDebug()->log("Admin-ui Action: <b>".$action."</b>"); 2266 2267 2268 2269 2270 2271 2272 ob_start(); //catch any output 2273 $ret = $this->{$actionName}(); 2274 2275 2276 //Ajax XML/JSON communication 2277 if(e_AJAX_REQUEST && is_array($ret)) 2278 { 2279 $response_type = $this->getParam('ajax_response', 'xml'); 2280 ob_clean(); 2281 $js_helper = $response->getJsHelper(); 2282 foreach ($ret as $act => $data) 2283 { 2284 $js_helper->addResponse($data, $act); 2285 } 2286 $js_helper->sendResponse($response_type); 2287 } 2288 2289 $ret .= ob_get_clean(); 2290 2291 // Ajax text response 2292 if(e_AJAX_REQUEST) 2293 { 2294 $response_type = 'text'; 2295 $response->getJsHelper()->addResponse($ret)->sendResponse($response_type); 2296 } 2297 else 2298 { 2299 $response->appendBody($ret); 2300 } 2301 2302 return $response; 2303 } 2304 2305 public function E404Observer() 2306 { 2307 $this->getResponse()->setTitle(LAN_UI_404_TITLE_ERROR); 2308 } 2309 2310 public function E404Page() 2311 { 2312 return '<div class="center">'.LAN_UI_404_BODY_ERROR.'</div>'; 2313 } 2314 2315 2316 public function E404AjaxPage() 2317 { 2318 exit; 2319 } 2320 2321 2322 public function E403Observer() 2323 { 2324 $this->getResponse()->setTitle(LAN_UI_403_TITLE_ERROR); 2325 } 2326 2327 public function E403Page() 2328 { 2329 return '<div class="center">'.LAN_UI_403_BODY_ERROR.'</div>'; 2330 } 2331 2332 2333 public function E403AjaxPage() 2334 { 2335 exit; 2336 } 2337 2338 /** 2339 * Generic redirect handler, it handles almost everything we would need. 2340 * Additionally, it moves currently registered system messages to SESSION message stack 2341 * In almost every case {@link redirectAction()} and {@link redirectMode()} are better solution 2342 * 2343 * @param string $action defaults to current action 2344 * @param string $mode defaults to current mode 2345 * @param string|array $exclude_query comma delimited variable names to be excluded from current query OR TRUE to exclude everything 2346 * @param string|array $merge_query query string (& delimiter) or associative array to be merged with current query 2347 * @param string $path default to e_SELF 2348 * @return void 2349 */ 2350 public function redirect($action = null, $mode = null, $exclude_query = '', $merge_query = array(), $path = null) 2351 { 2352 $request = $this->getRequest(); 2353 2354 if($mode) $request->setMode($mode); 2355 if($action) $request->setAction($action); 2356 if(!$path) $path = e_REQUEST_SELF; 2357 2358 //prevent cache 2359 header('Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0'); 2360 // header('Pragma: no-cache'); 2361 2362 $url = $path.'?'.$request->buildQueryString($merge_query, false, $exclude_query); 2363 // Transfer all messages to session 2364 e107::getMessage()->moveToSession(); 2365 // write session data 2366 session_write_close(); 2367 2368 // do redirect 2369 e107::redirect($url); 2370 // header('Location: '.$url); 2371 exit; 2372 } 2373 2374 /** 2375 * Convenient redirect() proxy method, make life easier when redirecting between actions 2376 * in same mode. 2377 * 2378 * @param string $action [optional] 2379 * @param string|array $exclude_query [optional] 2380 * @param string|array $merge_query [optional] 2381 * @return none 2382 */ 2383 public function redirectAction($action = null, $exclude_query = '', $merge_query = array()) 2384 { 2385 $this->redirect($action, null, $exclude_query, $merge_query); 2386 } 2387 2388 /** 2389 * Convenient redirect to another mode (doesn't use current Query state) 2390 * If path is empty, it'll be auto-detected from modes (dispatcher) array 2391 * 2392 * @param string $mode 2393 * @param string $action 2394 * @param string|array $query [optional] 2395 * @param string $path 2396 * @return void 2397 */ 2398 public function redirectMode($mode, $action, $query = array(), $path = null) 2399 { 2400 if(!$path && $this->getParam('modes')) 2401 { 2402 $modes = $this->getParam('modes'); 2403 if(vartrue($modes[$mode]) && vartrue($modes[$mode]['url'])) 2404 { 2405 $path = e107::getParser()->replaceConstants($modes[$mode]['url'], 'abs'); 2406 } 2407 } 2408 $this->redirect($action, $mode, true, $query, $path); 2409 } 2410 2411 /** 2412 * Convert action name to method name 2413 * 2414 * @param string $action_name formatted (e.g. request method getActionName()) action name 2415 * @param string $type page|observer|header|trigger 2416 * @param boolean $ajax force with true/false, if null will be auto-resolved 2417 * @return string 2418 */ 2419 public function toMethodName($action_name, $type= 'page', $ajax = null) 2420 { 2421 if(null === $ajax) $ajax = e_AJAX_REQUEST; //auto-resolving 2422 return $action_name.($ajax ? 'Ajax' : '').ucfirst(strtolower($type)); 2423 } 2424 2425 /** 2426 * Check if there is a trigger available in the posted data 2427 * @param array $exclude 2428 * @return boolean 2429 */ 2430 public function hasTrigger($exclude = array()) 2431 { 2432 $posted = array_keys($this->getPosted()); 2433 foreach ($posted as $key) 2434 { 2435 if(!in_array($key, $exclude) && strpos($key, 'etrigger_') === 0) 2436 { 2437 return true; 2438 } 2439 } 2440 return false; 2441 } 2442 2443 /** 2444 * Get default action 2445 * @return string action 2446 */ 2447 public function getDefaultAction() 2448 { 2449 return $this->_default_action; 2450 } 2451 2452 2453 2454 public function getDefaultTrigger() 2455 { 2456 return $this->_default_trigger; 2457 2458 } 2459 2460 /** 2461 * Set default action 2462 * @param string $action_name 2463 * @return e_admin_controller 2464 */ 2465 public function setDefaultAction($action_name) 2466 { 2467 $this->_default_action = $action_name; 2468 return $this; 2469 } 2470 2471 2472 /** 2473 * Set default trigger 2474 * @param string|array $triggers 'auto' or array of triggers 2475 * @example $triggers['submit'] = array(LAN_UPDATE, 'update', $model->getId()); 2476 $triggers['submit'] = array(LAN_CREATE, 'create', 0); 2477 $triggers['cancel'] = array(LAN_CANCEL, 'cancel'); 2478 * @return e_admin_controller 2479 */ 2480 public function setDefaultTrigger($triggers) 2481 { 2482 $this->_default_trigger = $triggers; 2483 return $this; 2484 } 2485 2486 /** 2487 * @return boolean 2488 */ 2489 public function triggersEnabled() 2490 { 2491 return $this->getParam('enable_triggers'); 2492 } 2493 2494 /** 2495 * @param boolean $flag 2496 * @return e_admin_controller 2497 */ 2498 public function setTriggersEnabled($flag) 2499 { 2500 $this->setParam('enable_triggers', $flag); 2501 return $this; 2502 } 2503} 2504 2505//FIXME - move everything from e_admin_ui except model auto-create related code 2506class e_admin_controller_ui extends e_admin_controller 2507{ 2508 2509 protected $table; 2510 /** 2511 * @var array UI field data 2512 */ 2513 2514 /** @var string */ 2515 protected $listQry; 2516 2517 protected $pid; 2518 2519 protected $fields = array(); 2520 2521 /** 2522 * @var array default fields activated on List view 2523 */ 2524 protected $fieldpref = array(); 2525 2526 /** 2527 * Custom Field (User) Preferences Name. (for viewable columns) 2528 * @var string 2529 */ 2530 protected $fieldPrefName = ''; 2531 2532 /** 2533 * @var array Plugin Preference description array 2534 */ 2535 protected $prefs = array(); 2536 2537 /** 2538 * Data required for _modifyListQry() to automate 2539 * db query building 2540 * @var array 2541 */ 2542 protected $tableJoin = array(); 2543 2544 /** 2545 * Array of table names and their aliases. (detected from listQry) 2546 * db query building 2547 * @var array 2548 */ 2549 protected $joinAlias = array(); 2550 2551 /** 2552 * Array of fields detected from listQry which are JOINs 2553 * @example returns array('user_name'=>'u.user_name'); from $listQry = "SELECT n.*,u.user_name FROM #news...."etc. 2554 */ 2555 protected $joinField = array(); 2556 2557 2558 /** 2559 * Main model table alias 2560 * @var string 2561 */ 2562 protected $tableAlias; 2563 2564 /** 2565 * @var string plugin name 2566 */ 2567 protected $pluginName; 2568 2569 2570 /** 2571 * @var string event name 2572 * base event trigger name to be used. Leave blank for no trigger. 2573 */ 2574 protected $eventName = null; 2575 2576 /** 2577 * @var string 2578 */ 2579 protected $defaultOrderField = null; 2580 2581 /** 2582 * @var string 2583 */ 2584 protected $defaultOrder = 'asc'; 2585 2586 /** 2587 * @var string SQL order, false to disable order, null is default order 2588 */ 2589 protected $listOrder = null; 2590 2591 /** 2592 * @var string SQL group-by field name (optional) 2593 */ 2594 protected $listGroup = null; 2595 2596 /** 2597 * @var string field containing the order number 2598 */ 2599 protected $sortField = null; 2600 2601 /** 2602 * @var string field containing the order number 2603 */ 2604 protected $treePrefix = null; 2605 2606 /** 2607 * @var string field containing the parent field 2608 */ 2609 protected $sortParent = null; 2610 2611 /** 2612 * @var int reorder step 2613 */ 2614 protected $orderStep = 1; 2615 2616 /** 2617 * Example: array('0' => 'Tab label', '1' => 'Another label'); 2618 * Referenced from $field property per field - 'tab => xxx' where xxx is the tab key (identifier) 2619 * @var array edit/create form tabs 2620 */ 2621 protected $tabs = array(); 2622 2623 /** 2624 * Example: array('0' => 'Tab label', '1' => 'Another label'); 2625 * Referenced from $prefs property per field - 'tab => xxx' where xxx is the tab key (identifier) 2626 * @var array edit/create form tabs 2627 */ 2628 protected $preftabs = array(); 2629 2630 /** 2631 * TODO Example: 2632 * Contains required data for auto-assembling URL from every record 2633 * For greater control - override url() method 2634 * @var array 2635 */ 2636 protected $url = array(); 2637 2638 /** 2639 * TODO Example: 2640 * Contains required data for mapping featurebox fields 2641 * @var array 2642 */ 2643 protected $featurebox = array(); 2644 2645 /** 2646 * Structure same as TreeModel parameters used for building the load() SQL 2647 * @var additional SQL to be applied when auto-building the list query 2648 */ 2649 protected $listQrySql = array(); 2650 2651 /** 2652 * @var Custom Filter SQL Query override. 2653 */ 2654 protected $filterQry = null; 2655 2656 /** 2657 * @var boolean 2658 */ 2659 protected $batchDelete = true; 2660 2661 /** 2662 * @var boolean 2663 */ 2664 protected $batchCopy = false; 2665 2666 /** 2667 * @var boolean 2668 */ 2669 protected $batchLink = false; 2670 2671 /** 2672 * @var boolean 2673 */ 2674 protected $batchFeaturebox = false; 2675 2676 /** 2677 * @var boolean 2678 */ 2679 protected $batchExport = false; 2680 2681 /** 2682 * @var array 2683 */ 2684 protected $batchOptions = array(); 2685 2686 /** 2687 * Could be LAN constant (mulit-language support) 2688 * 2689 * @var string plugin name 2690 */ 2691 protected $pluginTitle; 2692 2693 /** 2694 * Default (db) limit value 2695 * @var integer 2696 */ 2697 protected $perPage = 20; 2698 2699 2700 /** 2701 * Data for grid layout. 2702 * @var array 2703 */ 2704 protected $grid = array(); 2705 2706 /** 2707 * @var e_admin_model 2708 */ 2709 protected $formQuery = false; // custom form post query 2710 2711 /** 2712 * @var e_admin_model 2713 */ 2714 protected $_model = null; 2715 2716 /** 2717 * @var e_admin_tree_model 2718 */ 2719 protected $_tree_model = null; 2720 2721 /** 2722 * @var e_admin_tree_model 2723 */ 2724 protected $_ui = null; 2725 2726 /** 2727 * @var e_plugin_pref|e_core_pref 2728 */ 2729 protected $_pref = null; 2730 2731 /** 2732 * Prevent parsing table aliases more than once 2733 * @var boolean 2734 */ 2735 protected $_alias_parsed = false; 2736 2737 /** 2738 * @var bool 2739 */ 2740 protected $afterSubmitOptions = true; 2741 2742 public function getAfterSubmitOptions() 2743 { 2744 return $this->afterSubmitOptions; 2745 } 2746 2747 public function getBatchDelete() 2748 { 2749 return $this->batchDelete; 2750 } 2751 2752 public function getBatchCopy() 2753 { 2754 return $this->batchCopy; 2755 } 2756 2757 2758 public function getBatchLink() 2759 { 2760 return $this->batchLink; 2761 } 2762 2763 2764 public function getBatchFeaturebox() 2765 { 2766 return $this->batchFeaturebox; 2767 } 2768 2769 public function getBatchExport() 2770 { 2771 return $this->batchExport; 2772 } 2773 2774 public function getBatchOptions() 2775 { 2776 return $this->batchOptions; 2777 } 2778 2779 2780 /** 2781 * @return string 2782 */ 2783 public function getEventName() 2784 { 2785 return $this->eventName; 2786 } 2787 2788 2789 /** 2790 * @return string 2791 */ 2792 public function getPluginName() 2793 { 2794 return $this->pluginName; 2795 } 2796 2797 /** 2798 * @return string 2799 */ 2800 public function getPluginTitle() 2801 { 2802 return deftrue($this->pluginTitle, $this->pluginTitle); 2803 } 2804 2805 /** 2806 * Get Sort Field data 2807 * @return string 2808 */ 2809 public function getSortField() 2810 { 2811 return $this->sortField; 2812 } 2813 2814 /** 2815 * Get Sort Field data 2816 * @return string 2817 */ 2818 public function getSortParent() 2819 { 2820 return $this->sortParent; 2821 } 2822 2823 2824 2825 /** 2826 * Get Sort Field data 2827 * @return string 2828 */ 2829 public function getTreePrefix() 2830 { 2831 return $this->treePrefix; 2832 } 2833 2834 /** 2835 * Get Tab data 2836 * @return array 2837 */ 2838 public function getTabs() 2839 { 2840 return $this->tabs; 2841 } 2842 2843 public function addTab($key,$val) 2844 { 2845 $this->tabs[$key] = (string) $val; 2846 } 2847 2848 /** 2849 * Get Tab data 2850 * @return array 2851 */ 2852 public function getPrefTabs() 2853 { 2854 return $this->preftabs; 2855 } 2856 2857 /** 2858 * Get URL profile 2859 * @return array 2860 */ 2861 public function getUrl() 2862 { 2863 return $this->url; 2864 } 2865 2866 2867 /** 2868 * Get Featurebox Copy 2869 * @return array 2870 */ 2871 public function getFeaturebox() 2872 { 2873 return $this->featurebox; 2874 } 2875 2876 2877 /** 2878 * Get all field data 2879 * @return array 2880 */ 2881 public function getFields() 2882 { 2883 return $this->fields; 2884 } 2885 2886 /** 2887 * 2888 * @param string $field 2889 * @param string $key attribute name 2890 * @param mixed $default default value if not set, default is null 2891 * @return mixed 2892 */ 2893 public function getFieldAttr($field, $key = null, $default = null) 2894 { 2895 if(isset($this->fields[$field])) 2896 { 2897 if(null !== $key) 2898 { 2899 return isset($this->fields[$field][$key]) ? $this->fields[$field][$key] : $default; 2900 } 2901 return $this->fields[$field]; 2902 } 2903 return $default; 2904 } 2905 2906 /** 2907 * 2908 * @param string|array $field 2909 * @param string $key attribute name 2910 * @param mixed $value default value if not set, default is null 2911 * @return e_admin_controller_ui 2912 */ 2913 public function setFieldAttr($field, $key = null, $value = null) 2914 { 2915 // add field array 2916 if(is_array($field)) 2917 { 2918 foreach ($field as $f => $atts) 2919 { 2920 $this->setFieldAttr($f, $atts); 2921 } 2922 return $this; 2923 } 2924 // remove a field 2925 if(null === $key) 2926 { 2927 unset($this->fields[$field]); 2928 return $this; 2929 } 2930 // add to attribute array of a field 2931 if(is_array($key)) 2932 { 2933 foreach ($key as $k => $att) 2934 { 2935 $this->setFieldAttr($field, $k, $att); 2936 } 2937 return $this; 2938 } 2939 // remove attribute from field attribute set 2940 if(null === $value && $key != 'type') 2941 { 2942 unset($this->fields[$field][$key]); 2943 return $this; 2944 } 2945 // set attribute value 2946 $this->fields[$field][$key] = $value; 2947 return $this; 2948 } 2949 2950 /** 2951 * Get fields stored as user preferences 2952 * @return array 2953 */ 2954 public function getFieldPref() 2955 { 2956 return $this->fieldpref; 2957 } 2958 2959 /** 2960 * Get Config data array 2961 * @return array 2962 */ 2963 public function getPrefs() 2964 { 2965 return $this->prefs; 2966 } 2967 2968 public function getPerPage() 2969 { 2970 2971 if($this->getAction() === 'grid') 2972 { 2973 if($this->getGrid('carousel') === true) 2974 { 2975 return 0; 2976 } 2977 2978 return $this->getGrid('perPage'); 2979 } 2980 2981 2982 return $this->perPage; 2983 } 2984 2985 public function getGrid($key=null) 2986 { 2987 if($key !== null) 2988 { 2989 return $this->grid[$key]; 2990 } 2991 2992 return $this->grid; 2993 } 2994 2995 2996 public function getFormQuery() 2997 { 2998 return $this->formQuery; 2999 } 3000 3001 public function getPrimaryName() 3002 { 3003 return $this->getModel()->getFieldIdName(); 3004 } 3005 3006 3007 public function getDefaultOrderField() 3008 { 3009 return ($this->defaultOrder ? $this->defaultOrderField : $this->getPrimaryName()); 3010 } 3011 3012 public function getDefaultOrder() 3013 { 3014 return ($this->defaultOrder ? $this->defaultOrder : 'asc'); 3015 } 3016 3017 /** 3018 * Get column preference array 3019 * @return array 3020 */ 3021 public function getUserPref() 3022 { 3023 //global $user_pref; 3024 // return vartrue($user_pref['admin_cols_'.$this->getTableName()], array()); 3025 3026 $name = (!empty($this->fieldPrefName)) ? strtolower($this->pluginName."_".$this->fieldPrefName) : $this->getTableName(); 3027 3028 e107::getMessage()->addDebug("Loading Field Preferences using name: ".$name); 3029 $this->_log("Loading Field Preferences using name: ".$name); 3030 return e107::getUser()->getPref('admin_cols_'.$name, array()); 3031 } 3032 3033 /** 3034 * Set column preference array 3035 * @return boolean success 3036 */ 3037 public function setUserPref($new, $name='') 3038 { 3039 //global $user_pref; 3040 //e107::getUser()->getConfig()->setData($new); 3041 //$user_pref['admin_cols_'.$this->getTableName()] = $new; 3042 //$this->fieldpref = $new; 3043 //return save_prefs('user'); 3044 if(!empty($new)) 3045 { 3046 $this->fieldpref = $new; 3047 } 3048 3049 if(empty($name)) 3050 { 3051 $name = $this->getTableName(); 3052 } 3053 else 3054 { 3055 $name = strtolower($this->pluginName."_".$name); 3056 } 3057 3058 $msg = "Saving User Field preferences using name: ".$name; 3059 e107::getMessage()->addDebug($msg); 3060 $this->_log($msg); 3061 3062 return e107::getUser()->getConfig() 3063 ->set('admin_cols_'.$name, $new) 3064 ->save(); 3065 } 3066 3067 /** 3068 * Get current model 3069 * 3070 * @return e_admin_model 3071 */ 3072 public function getModel() 3073 { 3074 if(null === $this->_model) 3075 { 3076 $this->_setModel(); 3077 } 3078 3079 return $this->_model; 3080 } 3081 3082 3083 /** 3084 * Alias for getModel()->get and getListModel()->get(). 3085 * May be used inside field-method in read/write mode. 3086 * 3087 * @param string $key 3088 * @return mixed|null - current value of the chosen db field. 3089 */ 3090 public function getFieldVar($key = null) 3091 { 3092 if(empty($key)) 3093 { 3094 return null; 3095 } 3096 3097 if($this->getAction() === 'list' || $this->getAction() === 'grid') 3098 { 3099 $obj = $this->getListModel(); 3100 if(is_object($obj)) 3101 { 3102 return $obj->get($key); 3103 } 3104 3105 return null; 3106 } 3107 3108 return $this->getModel()->get($key); 3109 3110 } 3111 3112 3113 /** 3114 * Set controller model 3115 * @param e_admin_model $model 3116 * @return e_admin_controller_ui 3117 */ 3118 public function setModel($model) 3119 { 3120 $this->_model = $model; 3121 return $this; 3122 } 3123 3124 /** 3125 * Get model validation array 3126 * @return array 3127 */ 3128 public function getValidationRules() 3129 { 3130 return $this->getModel()->getValidationRules(); 3131 } 3132 3133 /** 3134 * Get model data field array 3135 * @return array 3136 */ 3137 public function getDataFields() 3138 { 3139 return $this->getModel()->getDataFields(); 3140 } 3141 3142 /** 3143 * Get model table or alias 3144 * @param boolean $alias get table alias on true, default false 3145 * @param object $prefix add e107 special '#' prefix, default false 3146 * @return string 3147 */ 3148 public function getTableName($alias = false, $prefix = false) 3149 { 3150 if($alias) return ($this->tableAlias ? $this->tableAlias : ''); 3151 return ($prefix ? '#' : '').$this->getModel()->getModelTable(); 3152 } 3153 3154 public function getIfTableAlias($prefix = false, $quote = false) //XXX May no longer by useful. see joinAlias() 3155 { 3156 $alias = $this->getTableName(true); 3157 if($alias) 3158 { 3159 return $alias; 3160 } 3161 return ( !$quote ? $this->getTableName(false, $prefix) : '`'.$this->getTableName(false, $prefix).'`' ); 3162 } 3163 3164 /** 3165 * Get join table data - XXX DEPRECATE? 3166 * @param string $table if null all data will be returned 3167 * @param string $att_name search for specific attribute, default null (no search) 3168 * @return mixed 3169 */ 3170 public function getJoinData($table = null, $att_name = null, $default_att = null) 3171 { 3172 if(null === $table) 3173 { 3174 return $this->tableJoin; 3175 } 3176 if(null === $att_name) 3177 { 3178 return (isset($this->tableJoin[$table]) ? $this->tableJoin[$table] : array()); 3179 } 3180 return (isset($this->tableJoin[$table][$att_name]) ? $this->tableJoin[$table][$att_name] : $default_att); 3181 } 3182 3183 public function setJoinData($table, $data) //XXX - DEPRECATE? 3184 { 3185 if(null === $data) 3186 { 3187 unset($this->tableJoin[$table]); 3188 return $this; 3189 } 3190 $this->tableJoin[$table] = (array) $data; 3191 return $this; 3192 } 3193 3194 /** 3195 * User defined model setter 3196 * @return e_admin_controller_ui 3197 */ 3198 protected function _setModel() 3199 { 3200 return $this; 3201 } 3202 3203 /** 3204 * Get current tree model 3205 * @return e_admin_tree_model 3206 */ 3207 public function getTreeModel() 3208 { 3209 if(null === $this->_tree_model) 3210 { 3211 $this->_setTreeModel(); 3212 } 3213 3214 return $this->_tree_model; 3215 } 3216 3217 /** 3218 * Get ordered models by their parents 3219 * add extra 3220 * @lonalore 3221 * @return e_admin_tree_model 3222 */ 3223 public function getTreeModelSorted() 3224 { 3225 $tree = $this->getTreeModel(); 3226 3227 $parentField = $this->getSortParent(); 3228 $orderField = $this->getSortField(); 3229 3230 $arr = array(); 3231 /** 3232 * @var $id 3233 * @var e_tree_model $model 3234 */ 3235 foreach ($tree->getTree() as $id => $model) 3236 { 3237 $parent = $model->get($parentField); 3238 $order = $model->get($orderField); 3239 3240 $model->set('_depth', '9999'); // include extra field in output, just as the MySQL function did. 3241 3242 3243 $arr[$id] = $model; 3244 } 3245 3246 3247 // usort($arr); array_multisort() ? 3248 3249 $tree->setTree($arr,true); // set the newly ordered tree. 3250 3251 // var_dump($arr); 3252 3253 return $this->_tree_model; 3254 } 3255 3256 3257 /** 3258 * @lonalore - found online. 3259 * @param string $idField The item's ID identifier (required) 3260 * @param string $parentField The item's parent identifier (required) 3261 * @param array $els The array (required) 3262 * @param int $parentID The parent ID for which to sort (internal) 3263 * @param array $result The result set (internal) 3264 * @param int $depth The depth (internal) 3265 * @return array 3266 */ 3267 function parentChildSort_r($idField, $parentField, $els=array(), $parentID = 0, &$result = array(), &$depth = 0) 3268 { 3269 foreach ($els as $key => $value) 3270 { 3271 if ($value[$parentField] == $parentID) 3272 { 3273 $value['depth'] = $depth; 3274 array_push($result, $value); 3275 unset($els[$key]); 3276 $oldParent = $parentID; 3277 $parentID = $value[$idField]; 3278 $depth++; 3279 $this->parentChildSort_r($idField,$parentField, $els, $parentID, $result, $depth); 3280 $parentID = $oldParent; 3281 $depth--; 3282 } 3283 } 3284 3285 return $result; 3286 } 3287 3288 3289 3290 3291 /** 3292 * Set controller tree model 3293 * @param e_admin_tree_model $tree_model 3294 * @return e_admin_controller_ui 3295 */ 3296 public function setTreeModel($tree_model) 3297 { 3298 $this->_tree_model = $tree_model; 3299 3300 return $this; 3301 } 3302 3303 /** 3304 * Get currently parsed model while in list mode 3305 * Model instance is registered by e_form::renderListForm() 3306 * 3307 * @return e_admin_model 3308 */ 3309 public function getListModel() 3310 { 3311 return e107::getRegistry('core/adminUI/currentListModel'); 3312 } 3313 3314 public function setListModel($model) 3315 { 3316 e107::setRegistry('core/adminUI/currentListModel', $model); 3317 return $this; 3318 } 3319 3320 /** 3321 * User defined tree model setter 3322 * @return e_admin_controller_ui 3323 */ 3324 protected function _setTreeModel() 3325 { 3326 return $this; 3327 } 3328 3329 /** 3330 * Get extended (UI) Form instance 3331 * 3332 * @return e_admin_form_ui 3333 */ 3334 public function getUI() 3335 { 3336 if(null === $this->_ui) 3337 { 3338 $this->_setUI(); 3339 } 3340 return $this->_ui; 3341 } 3342 3343 /** 3344 * Set controller UI form 3345 * @param e_admin_form_ui $ui 3346 * @return e_admin_controller_ui 3347 */ 3348 public function setUI($ui) 3349 { 3350 $this->_ui = $ui; 3351 return $this; 3352 } 3353 3354 /** 3355 * User defined UI form setter 3356 * @return e_admin_controller_ui 3357 */ 3358 protected function _setUI() 3359 { 3360 return $this; 3361 } 3362 3363 /** 3364 * Get Config object 3365 * @return e_plugin_pref or e_core_pref when used in core areas 3366 */ 3367 public function getConfig() 3368 { 3369 if(null === $this->_pref) 3370 { 3371 $this->_setConfig(); 3372 } 3373 return $this->_pref; 3374 } 3375 3376 /** 3377 * Set Config object 3378 * @return e_admin_controller_ui 3379 */ 3380 public function setConfig($config) 3381 { 3382 $this->_prefs = $config; 3383 return $this; 3384 } 3385 3386 3387 /** 3388 * @param $val 3389 */ 3390 public function setBatchDelete($val) 3391 { 3392 $this->batchDelete = $val; 3393 return $this; 3394 } 3395 3396 3397 3398 /** 3399 * @param $val 3400 */ 3401 public function setBatchCopy($val) 3402 { 3403 $this->batchCopy = $val; 3404 return $this; 3405 } 3406 3407 3408 /** 3409 * User defined config setter 3410 * @return e_admin_controller_ui 3411 */ 3412 protected function _setConfig() 3413 { 3414 return $this; 3415 } 3416 3417 /** 3418 * Manage column visibility 3419 * @param string $batch_trigger 3420 * @return null 3421 */ 3422 public function manageColumns() 3423 { 3424 $cols = array(); 3425 $posted = $this->getPosted('e-columns', array()); 3426 foreach ($this->getFields() as $field => $attr) 3427 { 3428 if((/*vartrue($attr['forced']) || */ in_array($field, $posted)) && !vartrue($attr['nolist'])) 3429 { 3430 $cols[] = $field; 3431 continue; 3432 } 3433 } 3434 3435 // Alow for an empty array to be saved also, to reset to default. 3436 if($this->getPosted('etrigger_ecolumns', false)) // Column Save Button 3437 { 3438 $this->setUserPref($cols, $this->fieldPrefName); 3439 e107::getMessage()->addDebug("User Field Preferences Saved: ".print_a($cols,true)); 3440 } 3441 } 3442 3443 3444 3445 /** 3446 * Handle posted batch options routine 3447 * @param string $batch_trigger 3448 * @return e_admin_controller_ui 3449 */ 3450 protected function _handleListBatch($batch_trigger) 3451 { 3452 $tp = e107::getParser(); 3453 //$multi_name = vartrue($this->fields['checkboxes']['toggle'], 'multiselect'); 3454 $multi_name = $this->getFieldAttr('checkboxes', 'toggle', 'multiselect'); 3455 $selected = array_values($this->getPosted($multi_name, array())); 3456 $trigger = $tp->toDB(explode('__', $batch_trigger)); 3457 3458 if(!empty($selected)) 3459 { 3460 foreach ($selected as $i => $_sel) 3461 { 3462 $selected[$i] = preg_replace('/[^\w\-:.]/', '', $_sel); 3463 } 3464 } 3465 3466 // XXX An empty selection should always be permitted for custom batch methods which may apply changes to all records, not only selected ones. 3467 3468 3469 if(substr($batch_trigger, 0, 6) === 'batch_') 3470 { 3471 list($tmp,$plugin,$command) = explode("_",$batch_trigger,3); 3472 $this->setPosted(array()); 3473 $this->getRequest()->setAction('batch'); 3474 $cls = e107::getAddon($plugin,'e_admin',true); 3475 e107::callMethod($cls,'process',$this,array('cmd'=>$command,'ids'=>$selected)); 3476 return $this; 3477 } 3478 3479 3480 $this->setTriggersEnabled(false); //disable further triggering 3481 3482 $actionName = $this->getRequest()->getActionName(); 3483 3484 if($actionName === 'Grid') 3485 { 3486 $actionName = 'List'; 3487 } 3488 3489 3490 switch($trigger[0]) 3491 { 3492 3493 case 'sefgen': 3494 $field = $trigger[1]; 3495 $value = $trigger[2]; 3496 3497 //handleListBatch(); for custom handling of all field names 3498 if(empty($selected)) return $this; 3499 $method = 'handle'.$actionName.'SefgenBatch'; 3500 if(method_exists($this, $method)) // callback handling 3501 { 3502 $this->$method($selected, $field, $value); 3503 } 3504 break; 3505 3506 3507 case 'export': 3508 if(empty($selected)) return $this; 3509 $method = 'handle'.$actionName.'ExportBatch'; 3510 if(method_exists($this, $method)) // callback handling 3511 { 3512 $this->$method($selected); 3513 } 3514 3515 break; 3516 3517 case 'delete': 3518 //method handleListDeleteBatch(); for custom handling of 'delete' batch 3519 // if(empty($selected)) return $this; 3520 // don't check selected data - subclass need to check additional post variables(confirm screen) 3521 3522 if(empty($selected) && !$this->getPosted('etrigger_delete_confirm')) // it's a delete batch, confirm screen 3523 { 3524 $params = $this->getFieldAttr($trigger[1], 'writeParms', array()); 3525 if(!is_array($params)) parse_str($params, $params); 3526 if(!vartrue($params['batchNoCheck'])) 3527 { 3528 return $this; 3529 } 3530 } 3531 3532 $method = 'handle'.$actionName.'DeleteBatch'; 3533 if(method_exists($this, $method)) // callback handling 3534 { 3535 $this->$method($selected); 3536 } 3537 break; 3538 3539 case 'bool': 3540 if(empty($selected)) return $this; 3541 $field = $trigger[1]; 3542 $value = $trigger[2] ? 1 : 0; 3543 //something like handleListBoolBatch(); for custom handling of 'bool' batch 3544 $method = 'handle'.$actionName.'BoolBatch'; 3545 if(method_exists($this, $method)) // callback handling 3546 { 3547 $this->$method($selected, $field, $value); 3548 } 3549 break; 3550 3551 case 'boolreverse': 3552 if(empty($selected)) return $this; 3553 $field = $trigger[1]; 3554 //something like handleListBoolreverseBatch(); for custom handling of 'boolreverse' batch 3555 $method = 'handle'.$actionName.'BoolreverseBatch'; 3556 if(method_exists($this, $method)) // callback handling 3557 { 3558 $this->$method($selected, $field); 3559 } 3560 break; 3561 3562 // see commma, userclasses batch options 3563 case 'attach': 3564 case 'deattach': 3565 case 'addAll': 3566 case 'clearAll': 3567 if(empty($selected)) return $this; 3568 $field = $trigger[1]; 3569 $value = $trigger[2]; 3570 3571 if($trigger[0] === 'addAll') 3572 { 3573 $parms = $this->getFieldAttr($field, 'writeParms', array()); 3574 if(!is_array($parms)) parse_str($parms, $parms); 3575 unset($parms['__options']); 3576 $value = $parms; 3577 if(empty($value)) return $this; 3578 if(!is_array($value)) $value = array_map('trim', explode(',', $value)); 3579 } 3580 3581 if(method_exists($this, 'handleCommaBatch')) 3582 { 3583 $this->handleCommaBatch($selected, $field, $value, $trigger[0]); 3584 } 3585 break; 3586 3587 // append to userclass list 3588 case 'ucadd': 3589 case 'ucremove': 3590 if(empty($selected)) return $this; 3591 $field = $trigger[1]; 3592 $class = $trigger[2]; 3593 $user = e107::getUser(); 3594 $e_userclass = e107::getUserClass(); 3595 3596 // check userclass manager class 3597 if (!isset($e_userclass->class_tree[$class]) || !$user->checkClass($e_userclass->class_tree[$class])) 3598 { 3599 return $this; 3600 } 3601 3602 if(method_exists($this, 'handleCommaBatch')) 3603 { 3604 $trigger[0] = $trigger[0] === 'ucadd' ? 'attach' : 'deattach'; 3605 $this->handleCommaBatch($selected, $field, $class, $trigger[0]); 3606 } 3607 break; 3608 3609 // add all to userclass list 3610 // clear userclass list 3611 case 'ucaddall': 3612 case 'ucdelall': 3613 if(empty($selected)) return $this; 3614 $field = $trigger[1]; 3615 $user = e107::getUser(); 3616 $e_userclass = e107::getUserClass(); 3617 $parms = $this->getFieldAttr($field, 'writeParms', array()); 3618 if(!is_array($parms)) parse_str($parms, $parms); 3619 if(!vartrue($parms['classlist'])) return $this; 3620 3621 $classes = $e_userclass->uc_required_class_list($parms['classlist']); 3622 foreach ($classes as $id => $label) 3623 { 3624 // check userclass manager class 3625 if (!isset($e_userclass->class_tree[$id]) || !$user->checkClass($e_userclass->class_tree[$id])) 3626 { 3627 $msg = $tp->lanVars(LAN_NO_ADMIN_PERMISSION,$label); 3628 $this->getTreeModel()->addMessageWarning($msg); 3629 unset($classes[$id],$msg); 3630 } 3631 } 3632 if(method_exists($this, 'handleCommaBatch')) 3633 { 3634 $this->handleCommaBatch($selected, $field, array_keys($classes), $trigger[0] === 'ucdelall' ? 'clearAll' : 'addAll'); 3635 } 3636 break; 3637 3638 // handleListCopyBatch etc. 3639 default: 3640 $field = $trigger[0]; 3641 $value = $trigger[1]; 3642 3643 //something like handleListUrlTypeBatch(); for custom handling of 'url_type' field name 3644 $method = 'handle'.$actionName.$this->getRequest()->camelize($field).'Batch'; 3645 3646 e107::getMessage()->addDebug("Searching for custom batch method: ".$method."(".$selected.",".$value.")"); 3647 3648 if(method_exists($this, $method)) // callback handling 3649 { 3650 $this->$method($selected, $value); 3651 break; 3652 } 3653 3654 //handleListBatch(); for custom handling of all field names 3655 //if(empty($selected)) return $this; 3656 $method = 'handle'.$actionName.'Batch'; 3657 e107::getDebug()->log("Checking for batch method: ".$method); 3658 if(method_exists($this, $method)) 3659 { 3660 $this->$method($selected, $field, $value); 3661 } 3662 3663 3664 3665 break; 3666 } 3667 return $this; 3668 } 3669 3670 /** 3671 * Handle requested filter dropdown value 3672 * @param string $filter_value 3673 * @return array field -> value 3674 */ 3675 protected function _parseFilterRequest($filter_value) 3676 { 3677 $tp = e107::getParser(); 3678 if(!$filter_value || $filter_value === '___reset___') 3679 { 3680 return array(); 3681 } 3682 $filter = (array) $tp->toDB(explode('__', $filter_value)); 3683 $res = array(); 3684 switch($filter[0]) 3685 { 3686 case 'bool': 3687 // direct query 3688 $res = array($filter[1], $filter[2]); 3689 $this->_log("listQry Filtered by ".$filter[1]." (".($filter[2] ? 'true': 'false').")"); 3690 break; 3691 3692 case 'datestamp': 3693 3694 //XXX DO NOT TRANSLATE THESE VALUES! 3695 $dateConvert = array( 3696 "hour" => "1 hour ago", 3697 "day" => "24 hours ago", 3698 "week" => "1 week ago", 3699 "month" => "1 month ago", 3700 "month3" => "3 months ago", 3701 "month6" => "6 months ago", 3702 "month9" => "9 months ago", 3703 "year" => "1 year ago", 3704 "nhour" => "now + 1 hour", 3705 "nday" => "now + 24 hours", 3706 "nweek" => "now + 1 week", 3707 "nmonth" => "now + 1 month", 3708 "nmonth3" => "now + 3 months", 3709 "nmonth6" => "now + 6 months", 3710 "nmonth9" => "now + 9 months", 3711 "nyear" => "now + 1 year", 3712 ); 3713 3714 $ky = $filter[2]; 3715 $time = vartrue($dateConvert[$ky]); 3716 $timeStamp = strtotime($time); 3717 3718 $res = array($filter[1], $timeStamp); 3719 3720 $this->_log("listQry Filtered by ".$filter[1]." (".$time.")"); 3721 3722 break; 3723 3724 default: 3725 //something like handleListUrlTypeFilter(); for custom handling of 'url_type' field name filters 3726 $method = 'handle'.$this->getRequest()->getActionName().$this->getRequest()->camelize($filter[0]).'Filter'; 3727 $args = array_slice($filter, 1); 3728 3729 e107::getMessage()->addDebug("Searching for custom filter method: ".$method."(".implode(', ', $args).")"); 3730 3731 3732 if(method_exists($this, $method)) // callback handling 3733 { 3734 //return $this->$method($filter[1], $selected); selected? 3735 // better approach - pass all values as method arguments 3736 // NOTE - callbacks are allowed to return QUERY as a string, it'll be added in the WHERE clause 3737 3738 e107::getMessage()->addDebug('Executing filter callback <strong>'.get_class($this).'::'.$method.'('.implode(', ', $args).')</strong>'); 3739 3740 return call_user_func_array(array($this, $method), $args); 3741 } 3742 else // default handling 3743 { 3744 $res = array($filter[0], $filter[1]); 3745 $this->_log("listQry Filtered by ".$filter[0]." (".$filter[1].")"); 3746 } 3747 break; 3748 } 3749 3750 //print_a($res); 3751 //exit; 3752 3753 return $res; 3754 } 3755 3756 3757 /** 3758 * Convert posted to model values after submit (based on field type) 3759 * @param array $data 3760 * @return void 3761 */ 3762 protected function convertToData(&$data) 3763 { 3764 $model = new e_model($data); 3765 3766 foreach ($this->getFields() as $key => $attributes) 3767 { 3768 $value = vartrue($attributes['dataPath']) ? $model->getData($attributes['dataPath']) : $model->get($key); 3769 3770 if(null === $value) 3771 { 3772 continue; 3773 } 3774 switch($attributes['type']) 3775 { 3776 3777 case 'password': //TODO more encryption options. 3778 if(strlen($value) < 30) // expect a non-md5 value if less than 32 chars. 3779 { 3780 $value = md5($value); 3781 } 3782 3783 break; 3784 3785 3786 case 'datestamp': 3787 if(!is_numeric($value)) 3788 { 3789 if(!empty($attributes['writeParms'])) 3790 { 3791 if(is_string($attributes['writeParms'])) 3792 { 3793 parse_str($attributes['writeParms'],$opt); 3794 } 3795 elseif(is_array($attributes['writeParms'])) 3796 { 3797 $opt = $attributes['writeParms']; 3798 } 3799 } 3800 3801 3802 $format = $opt['type'] ? ('input'.$opt['type']) : 'inputdate'; 3803 $value = trim($value) ? e107::getDate()->toTime($value, $format) : 0; 3804 } 3805 break; 3806 3807 case 'ip': // TODO - ask Steve if this check is required 3808 //if(strpos($value, '.') !== FALSE) 3809 { 3810 $value = trim($value) ? e107::getIPHandler()->ipEncode($value) : ''; 3811 } 3812 break; 3813 3814 case 'dropdown': // TODO - ask Steve if this check is required 3815 case 'lanlist': 3816 case 'userclasses': 3817 case 'comma': 3818 case 'checkboxes': 3819 if(is_array($value)) 3820 { 3821 // no sanitize here - data is added to model posted stack 3822 // and validated & sanitized before sent to db 3823 //$value = array_map(array(e107::getParser(), 'toDB'), $value); 3824 $value = implode(',', $value); 3825 } 3826 break; 3827 3828 case 'images': 3829 case 'files': 3830 3831 // XXX Cam @ SecretR: didn't work here. See model_class.php line 2046. 3832 // if(!is_array($value)) 3833 // { 3834 // $value = e107::unserialize($value); 3835 // } 3836 break; 3837 3838 3839 } 3840/* 3841 if($attributes['serialize'] == true) 3842 { 3843 $attributes['data'] = 'array'; 3844 } 3845 3846 if($attributes['data'] != 'array') 3847 { 3848 $value = e107::unserialize($value); 3849 } 3850*/ 3851 3852 if(vartrue($attributes['dataPath'])) 3853 { 3854 $model->setData($attributes['dataPath'], $value); 3855 } 3856 else 3857 { 3858 $model->set($key, $value); 3859 } 3860 3861 } 3862 3863 $data = $model->getData(); 3864 unset($model); 3865 $this->toData($data); 3866 } 3867 3868 /** 3869 * User defined method for converting POSTED to MODEL data 3870 * @param array $data posted data 3871 * @param string $type current action type - edit, create, list or user defined 3872 * @return void 3873 */ 3874 protected function toData(&$data, $type = '') 3875 { 3876 } 3877 3878 /** 3879 * Take approproate action after successfull submit 3880 * 3881 * @param integer $id optional, needed only if redirect action is 'edit' 3882 * @param string $noredirect_for don't redirect if action equals to its value 3883 */ 3884 protected function doAfterSubmit($id = 0, $noredirect_for = '') 3885 { 3886 if(e_AJAX_REQUEST) return; 3887 3888 if($noredirect_for && $noredirect_for == $this->getPosted('__after_submit_action') && $noredirect_for == $this->getAction()) 3889 { 3890 return; 3891 } 3892 3893 $choice = $this->getPosted('__after_submit_action', 0); 3894 switch ($choice) { 3895 case 'create': // create 3896 $this->redirectAction('create', 'id'); 3897 break; 3898 3899 case 'edit': // edit 3900 $this->redirectAction('edit', '', 'id='.$id); 3901 break; 3902 3903 case 'list': // list 3904 $this->redirectAction('list', 'id'); 3905 break; 3906 3907 default: 3908 $choice = explode('|', str_replace('{ID}', $id, $choice), 3); 3909 $this->redirectAction(preg_replace('/[^\w\-:.]/', '', $choice[0]), vartrue($choice[1]), vartrue($choice[2])); 3910 break; 3911 } 3912 return; 3913 } 3914 3915 /** 3916 * Build ajax auto-complete filter response 3917 * @return string response markup 3918 */ 3919 protected function renderAjaxFilterResponse($listQry = '') 3920 { 3921 $debug = false; 3922 $srch = $this->getPosted('searchquery'); 3923 $this->getRequest()->setQuery('searchquery', $srch); //_modifyListQry() is requiring GET String 3924 3925 $ret = '<ul>'; 3926 $ret .= '<li>'.$srch.'<span class="informal warning"> '.LAN_FILTER_LABEL_TYPED.'</span></li>'; // fix Enter - search for typed word only 3927 3928 $reswords = array(); 3929 if(trim($srch) !== '') 3930 { 3931 // Build query 3932 $qry = $this->_modifyListQry(false, true, 0, 20, $listQry); 3933 $this->_log("Filter ListQry: ".$qry); 3934 //file_put_contents(e_LOG.'uiAjaxResponseSQL.log', $qry."\n\n", FILE_APPEND); 3935 3936 // Make query 3937 $sql = e107::getDb(); 3938 if($qry && $sql->gen($qry, $debug)) 3939 { 3940 while ($res = $sql->fetch()) 3941 { 3942 $tmp1 = array(); 3943 $tmp = array_values(preg_grep('#'.$srch.'#i', $res)); 3944 foreach ($tmp as $w) 3945 { 3946 if($w == $srch) 3947 { 3948 array_unshift($reswords, $w); //exact match 3949 continue; 3950 } 3951 preg_match('#[\S]*('.$srch.')[\S]*#i', $w, $tmp1); 3952 if($tmp1[0]) $reswords[] = $tmp1[0]; 3953 } 3954 } 3955 } 3956 3957 // Build response 3958 $reswords = array_unique($reswords); 3959 if($reswords) 3960 { 3961 $ret .= '<li>'.implode("</li>\n\t<li>", $reswords).'</li>'; 3962 } 3963 } 3964 3965 $ret .= '<li><span class="informal warning"> '.LAN_FILTER_LABEL_CLEAR.' </span></li>'; // clear filter option 3966 $ret .= '</ul>'; 3967 return $ret; 3968 } 3969 3970 /** 3971 * Given an alias such as 'u' or 'n.news_datestamp' - will return the associated table such as 'user' or 'news' 3972 */ 3973 function getTableFromAlias($alias) 3974 { 3975 if(strpos($alias,".")!==false) 3976 { 3977 list($alias,$tmp) = explode(".",$alias,2); 3978 } 3979 3980 $tmp = array_flip($this->joinAlias); 3981 return vartrue($tmp[$alias]); 3982 } 3983 3984 public function getJoinField($field=null) 3985 { 3986 if(empty($field)) 3987 { 3988 return $this->joinField; 3989 } 3990 3991 return isset($this->joinField[$field]) ? $this->joinField[$field] : false; // vartrue($this->joinField[$field],false); 3992 } 3993 3994 public function getJoinAlias() 3995 { 3996 return $this->joinAlias; 3997 } 3998 3999 /** 4000 * Parses all available field data, adds internal attributes for handling join requests 4001 * @return e_admin_controller_ui 4002 */ 4003 protected function parseAliases() 4004 { 4005 if($this->_alias_parsed) return $this; // already parsed!!! 4006 4007 $this->joinAlias($this->listQry); // generate Table Aliases from listQry 4008 4009 if($this->getJoinData()) 4010 { 4011 foreach ($this->getJoinData() as $table => $att) 4012 { 4013 if(strpos($table, '.') !== false) 4014 { 4015 $tmp = explode('.', $table, 2); 4016 $this->setJoinData($table, null); 4017 $att['alias'] = $tmp[0]; 4018 $att['table'] = $tmp[1]; 4019 $att['__tablePath'] = $att['alias'].'.'; 4020 $att['__tableFrom'] = '`#'.$att['table'].'` AS '.$att['alias']; 4021 $this->setJoinData($att['alias'], $att); 4022 unset($tmp); 4023 continue; 4024 } 4025 $att['table'] = $table; 4026 $att['alias'] = ''; 4027 $att['__tablePath'] = '`#'.$att['table'].'`.'; 4028 $att['__tableFrom'] = '`#'.$att['table'].'`'; 4029 $this->setJoinData($table, $att); 4030 } 4031 } 4032 4033 4034 if(empty($this->fields)) 4035 { 4036 $this->_alias_parsed = true; 4037 return $this; 4038 } 4039 4040 4041 // check for table & field aliases 4042 $fields = array(); // preserve order 4043 foreach ($this->fields as $field => $att) 4044 { 4045 // fieldAlias.fieldName // table name no longer required as it's included in listQry. (see joinAlias() ) 4046 if(strpos($field, '.') !== false) // manually entered alias. 4047 { 4048 $tmp = explode('.', $field, 2); 4049 $table = $this->getTableFromAlias($tmp[0]); 4050 $att['table'] = $table; 4051 $att['alias'] = $tmp[0]; 4052 $att['field'] = $tmp[1]; 4053 $att['__tableField'] = $field; 4054 $att['__tablePath'] = $att['alias'].'.'; 4055 $att['__tableFrom'] = "`#".$table."`.".$tmp[1];//." AS ".$att['alias']; 4056 $field = $att['alias'] ? $tmp[1] : $tmp[0]; 4057 4058 $fields[$field] = $att; 4059 unset($tmp); 4060 } 4061 else 4062 { 4063 4064 $att['table'] = $this->getIfTableAlias(false); 4065 4066 if($newField = $this->getJoinField($field)) // Auto-Detect. 4067 { 4068 $table = $this->getTableFromAlias($newField); // Auto-Detect. 4069 $att['table'] = $table; 4070 $att['alias'] = $newField; 4071 $att['__tableField'] = $newField; 4072 // $att['__tablePath'] = $newField; ????!!!!! 4073 $att['__tableFrom'] = "`#".$table."`.".$field;//." AS ".$newField; 4074 } 4075 elseif(isset($this->joinAlias[$this->table]) && $field !='checkboxes' && $field !='options') 4076 { 4077 $att['alias'] = $this->joinAlias[$this->table].".".$field; 4078 } 4079 else 4080 { 4081 $att['alias'] = ""; 4082 } 4083 $att['field'] = $field; 4084 $fields[$field] = $att; 4085 } 4086 4087 if($fields[$field]['table'] == $this->getIfTableAlias(false)) 4088 { 4089 $fields[$field]['__tableField'] = $att['alias'] ? $att['alias'] : $this->getIfTableAlias(true, true).'.'.$att['field']; 4090 $fields[$field]['__tableFrom'] = $this->getIfTableAlias(true, true).'.'.$att['field'].($att['alias'] ? ' AS '.$att['alias'] : ''); 4091 } 4092 else 4093 { 4094 // $fields[$field]['__tableField'] = $this->getJoinData($fields[$field]['table'], '__tablePath').$field; 4095 } 4096 /* 4097 if($fields[$field]['table']) 4098 { 4099 if($fields[$field]['table'] == $this->getIfTableAlias(false)) 4100 { 4101 $fields[$field]['__tableField'] = $att['alias'] ? $att['alias'] : $this->getIfTableAlias(true, true).'.'.$att['field']; 4102 $fields[$field]['__tableFrom'] = $this->getIfTableAlias(true, true).'.'.$att['field'].($att['alias'] ? ' AS '.$att['alias'] : ''); 4103 } 4104 else 4105 { 4106 $fields[$field]['__tableField'] = $this->getJoinData($fields[$field]['table'], '__tablePath').$field; 4107 } 4108 } 4109 else 4110 { 4111 $fields[$field]['__tableField'] = '`'.$this->getTableName(false, true).'`.'.$field; 4112 } 4113 */ 4114 } 4115 4116 4117 $this->fields = $fields; 4118 4119 $this->_alias_parsed = true; 4120 return $this; 4121 } 4122 4123 /** 4124 * Intuitive LEFT JOIN Qry support. (preferred) 4125 * Generate array of table names and their alias - auto-detected from listQry; 4126 * eg. $listQry = "SELECT m.*, u.user_id,u.user_name FROM #core_media AS m LEFT JOIN #user AS u ON m.media_author = u.user_id"; 4127 */ 4128 public function joinAlias($listQry=null) 4129 { 4130 if(!empty($listQry)) 4131 { 4132 preg_match_all("/`?#([\w-]+)`?\s*(as|AS)\s*([\w-]+)/im",$listQry,$matches); 4133 $keys = array(); 4134 foreach($matches[1] AS $k=>$v) 4135 { 4136 if(varset($matches[3][$k]) && !array_key_exists($v, $this->joinAlias)) 4137 { 4138 $this->joinAlias[$v] = $matches[3][$k]; // array. eg $this->joinAlias['core_media'] = 'm'; 4139 } 4140 4141 $keys[] = $matches[3][$k]; 4142 } 4143 4144 foreach($keys as $alias) 4145 { 4146 preg_match_all("/".$alias."\.([\w]*)/i",$listQry,$match); 4147 foreach($match[1] as $k=>$m) 4148 { 4149 if(empty($m)) 4150 { 4151 continue; 4152 } 4153 $this->joinField[$m] = $match[0][$k]; 4154 } 4155 } 4156 4157 } 4158 elseif($this->tableJoin) 4159 { 4160 foreach ($this->tableJoin as $tbl => $data) 4161 { 4162 $matches = explode('.', $tbl, 2); 4163 $this->joinAlias[$matches[1]] = $matches[0]; // array. eg $this->joinAlias['core_media'] = 'm'; 4164 //'user_name'=>'u.user_name' 4165 if(isset($data['fields']) && $data['fields'] !== '*') 4166 { 4167 $tmp = explode(',', $data['fields']); 4168 foreach ($tmp as $field) 4169 { 4170 $this->joinField[$field] = $matches[0].'.'.$field; 4171 } 4172 } 4173 } 4174 } 4175 4176 4177 } 4178 4179 /** 4180 * Quick fix for bad custom $listQry; 4181 */ 4182 protected function parseCustomListQry($qry) 4183 { 4184 if(E107_DEBUG_LEVEL == E107_DBG_SQLQUERIES) 4185 { 4186 e107::getMessage()->addDebug('Using Custom listQry '); 4187 } 4188 4189 if(strpos($qry,'`')===false && strpos($qry, 'JOIN')===false) 4190 { 4191 $ret = preg_replace("/FROM\s*(#[\w]*)/","FROM `$1`", $qry); // backticks missing, so add them. 4192 4193 if($ret) 4194 { 4195 e107::getMessage()->addDebug('Your $listQry is missing `backticks` around the table name! It should look like this'. print_a($ret,true)); 4196 return $ret; 4197 } 4198 } 4199 4200 return $qry; 4201 } 4202 4203 /** 4204 * Fix search string by replacing the commonly used '*' wildcard 4205 * with the mysql represenation of it '%' and '?' with '_' (single character) 4206 * 4207 * @param string $search 4208 * @return string 4209 */ 4210 protected function fixSearchWildcards($search) 4211 { 4212 $search = trim($search); 4213 if (empty($search)) 4214 { 4215 return ''; 4216 } 4217 4218 // strip wildcard on the beginning and the end 4219 while (substr($search, 0, 1) == '*') $search = substr($search, 1); 4220 while (substr($search, -1) == '*') $search = substr($search, 0, -1); 4221 4222 // replace "*" wildcard with mysql wildcard "%" 4223 return str_replace(array('*', '?'), array('%', '_'), $search); 4224 } 4225 4226 4227 // TODO - abstract, array return type, move to parent? 4228 protected function _modifyListQry($raw = false, $isfilter = false, $forceFrom = false, $forceTo = false, $listQry = '') 4229 { 4230 $searchQry = array(); 4231 $filterFrom = array(); 4232 $request = $this->getRequest(); 4233 $tp = e107::getParser(); 4234 $tablePath = $this->getIfTableAlias(true, true).'.'; 4235 $tableFrom = '`'.$this->getTableName(false, true).'`'.($this->getTableName(true) ? ' AS '.$this->getTableName(true) : ''); 4236 $tableSFieldsArr = array(); // FROM for main table 4237 $tableSJoinArr = array(); // FROM for join tables 4238 $filter = array(); 4239 4240 $this->listQry = $listQry; 4241 4242 $filterOptions = $request->getQuery('filter_options', ''); 4243 4244 $searchQuery = $this->fixSearchWildcards($tp->toDB($request->getQuery('searchquery', ''))); 4245 $searchFilter = $this->_parseFilterRequest($filterOptions); 4246 4247 $listQry = $this->listQry; // check for modification during parseFilterRequest(); 4248 4249 if(E107_DEBUG_LEVEL == E107_DBG_SQLQUERIES) 4250 { 4251 e107::getMessage()->addDebug('searchQuery: <b>'.$searchQuery.'</b>'); 4252 } 4253 4254 if($searchFilter && is_array($searchFilter)) 4255 { 4256 4257 list($filterField, $filterValue) = $searchFilter; 4258 4259 if($filterField && $filterValue !== '' && isset($this->fields[$filterField])) 4260 { 4261 $_dataType = $this->fields[$filterField]['data']; 4262 $_fieldType = $this->fields[$filterField]['type']; 4263 4264 if($_fieldType === 'comma' || $_fieldType === 'checkboxes' || $_fieldType === 'userclasses' || ($_fieldType === 'dropdown' && !empty($this->fields[$filterField]['writeParms']['multiple']))) 4265 { 4266 $_dataType = 'set'; 4267 } 4268 4269 switch ($_dataType) 4270 { 4271 case 'set': 4272 $searchQry[] = "FIND_IN_SET('".$tp->toDB($filterValue)."', ".$this->fields[$filterField]['__tableField'].")"; 4273 break; 4274 4275 case 'int': 4276 case 'integer': 4277 if($_fieldType === 'datestamp') // Past Month, Past Year etc. 4278 { 4279 if($filterValue > time()) 4280 { 4281 $searchQry[] = $this->fields[$filterField]['__tableField']." > ".time(); 4282 $searchQry[] = $this->fields[$filterField]['__tableField']." < ".intval($filterValue); 4283 } 4284 else 4285 { 4286 $searchQry[] = $this->fields[$filterField]['__tableField']." > ".intval($filterValue); 4287 $searchQry[] = $this->fields[$filterField]['__tableField']." < ".time(); 4288 } 4289 4290 } 4291 else 4292 { 4293 $searchQry[] = $this->fields[$filterField]['__tableField']." = ".intval($filterValue); 4294 } 4295 break; 4296 4297 4298 4299 default: // string usually. 4300 4301 if($filterValue === '_ISEMPTY_') 4302 { 4303 $searchQry[] = $this->fields[$filterField]['__tableField']." = '' "; 4304 } 4305 4306 else 4307 { 4308 4309 if($_fieldType === 'method') // More flexible filtering. 4310 { 4311 4312 $searchQry[] = $this->fields[$filterField]['__tableField']." LIKE \"%".$tp->toDB($filterValue)."%\""; 4313 } 4314 else 4315 { 4316 4317 $searchQry[] = $this->fields[$filterField]['__tableField']." = '".$tp->toDB($filterValue)."'"; 4318 } 4319 } 4320 4321 //exit; 4322 break; 4323 } 4324 4325 } 4326 //echo 'type= '. $this->fields[$filterField]['data']; 4327 // print_a($this->fields[$filterField]); 4328 } 4329 elseif($searchFilter && is_string($searchFilter)) 4330 { 4331 4332 // filter callbacks could add to WHERE clause 4333 $searchQry[] = $searchFilter; 4334 } 4335 4336 if(E107_DEBUG_LEVEL == E107_DBG_SQLQUERIES) 4337 { 4338 e107::getMessage()->addDebug(print_a($searchQry,true)); 4339 } 4340 4341 $className = get_class($this); 4342 4343 // main table should select everything 4344 $tableSFieldsArr[] = $tablePath.'*'; 4345 foreach($this->getFields() as $key => $var) 4346 { 4347 // disabled or system 4348 if((!empty($var['nolist']) && empty($var['filter'])) || empty($var['type']) || empty($var['data'])) 4349 { 4350 continue; 4351 } 4352 4353 // select FROM... for main table 4354 if(!empty($var['alias']) && !empty($var['__tableField'])) 4355 { 4356 $tableSFieldsArr[] = $var['__tableField']; 4357 } 4358 4359 // filter for WHERE and FROM clauses 4360 $searchable_types = array('text', 'textarea', 'bbarea', 'url', 'ip', 'tags', 'email', 'int', 'integer', 'str', 'string', 'number'); //method? 'user', 4361 4362 if($var['type'] === 'method' && !empty($var['data']) && ($var['data'] === 'string' || $var['data'] === 'str' || $var['data'] === 'int')) 4363 { 4364 $searchable_types[] = 'method'; 4365 } 4366 4367 if(trim($searchQuery) !== '' && in_array($var['type'], $searchable_types) && $var['__tableField']) 4368 { 4369 // Search for customer filter handler. 4370 $cutomerSearchMethod = 'handle'.$this->getRequest()->getActionName().$this->getRequest()->camelize($key).'Search'; 4371 $args = array($tp->toDB($request->getQuery('searchquery', ''))); 4372 4373 e107::getMessage()->addDebug("Searching for custom search method: ".$className.'::'.$cutomerSearchMethod."(".implode(', ', $args).")"); 4374 4375 if(method_exists($this, $cutomerSearchMethod)) // callback handling 4376 { 4377 e107::getMessage()->addDebug('Executing custom search callback <strong>'.$className.'::'.$cutomerSearchMethod.'('.implode(', ', $args).')</strong>'); 4378 4379 $filter[] = call_user_func_array(array($this, $cutomerSearchMethod), $args); 4380 continue; 4381 } 4382 4383 4384 if($var['data'] === 'int' || $var['data'] === 'integer' || $var['type'] === 'int' || $var['type'] === 'integer') 4385 { 4386 if(is_numeric($searchQuery)) 4387 { 4388 $filter[] = $var['__tableField']." = ".$searchQuery; 4389 } 4390 continue; 4391 } 4392 4393 if($var['type'] === 'ip') 4394 { 4395 $ipSearch = e107::getIPHandler()->ipEncode($searchQuery); 4396 if(!empty($ipSearch)) 4397 { 4398 $filter[] = $var['__tableField']." LIKE '%".$ipSearch."%'"; 4399 } 4400 // Continue below for BC check also. 4401 } 4402 4403 4404 if(strpos($searchQuery, " ") !==false) // search multiple words across fields. 4405 { 4406 $tmp = explode(" ", $searchQuery); 4407 4408 if(count($tmp) < 4) // avoid excessively long query. 4409 { 4410 foreach($tmp as $splitSearchQuery) 4411 { 4412 if(!empty($splitSearchQuery)) 4413 { 4414 $filter[] = $var['__tableField']." LIKE '%".$splitSearchQuery."%'"; 4415 } 4416 } 4417 } 4418 else 4419 { 4420 $filter[] = $var['__tableField']." LIKE '%".$searchQuery."%'"; 4421 } 4422 4423 } 4424 else 4425 { 4426 $filter[] = $var['__tableField']." LIKE '%".$searchQuery."%'"; 4427 } 4428 4429 4430 if($isfilter) 4431 { 4432 $filterFrom[] = $var['__tableField']; 4433 4434 } 4435 } 4436 } 4437 4438 4439 if(strpos($filterOptions,'searchfield__') === 0) // search in specific field, so remove the above filters. 4440 { 4441 $filter = array(); // reset filter. 4442 } 4443 4444 4445 if(E107_DEBUG_LEVEL == E107_DBG_SQLQUERIES) 4446 { 4447 // e107::getDebug()->log(print_a($filter,true)); 4448 // e107::getMessage()->addInfo(print_a($filter,true)); 4449 } 4450 4451 if($isfilter) 4452 { 4453 if(!$filterFrom) return false; 4454 $tableSFields = implode(', ', $filterFrom); 4455 } 4456 else 4457 { 4458 $tableSFields = $tableSFieldsArr ? implode(', ', $tableSFieldsArr) : $tablePath.'*'; 4459 } 4460 4461 4462 $jwhere = array(); 4463 $joins = array(); 4464 //file_put_contents(e_LOG.'uiAjaxResponseSFields.log', $tableSFields."\n\n", FILE_APPEND); 4465 //file_put_contents(e_LOG.'uiAjaxResponseFields.log', print_r($this->getFields(), true)."\n\n", FILE_APPEND); 4466 if($this->getJoinData()) 4467 { 4468 $qry = "SELECT SQL_CALC_FOUND_ROWS ".$tableSFields; 4469 foreach ($this->getJoinData() as $jtable => $tparams) 4470 { 4471 // Select fields 4472 if(!$isfilter) 4473 { 4474 $fields = vartrue($tparams['fields']); 4475 if('*' === $fields) 4476 { 4477 $tableSJoinArr[] = "{$tparams['__tablePath']}*"; 4478 } 4479 elseif($fields) 4480 { 4481 $tableSJoinArr[] = $fields; 4482 /*$fields = explode(',', $fields); 4483 foreach ($fields as $field) 4484 { 4485 $qry .= ", {$tparams['__tablePath']}`".trim($field).'`'; 4486 }*/ 4487 } 4488 } 4489 4490 // Prepare Joins 4491 $joins[] = " 4492 ".vartrue($tparams['joinType'], 'LEFT JOIN')." {$tparams['__tableFrom']} ON ".(vartrue($tparams['leftTable']) ? $tparams['leftTable'].'.' : $tablePath)."`".vartrue($tparams['leftField'])."` = {$tparams['__tablePath']}`".vartrue($tparams['rightField'])."`".(vartrue($tparams['whereJoin']) ? ' '.$tparams['whereJoin'] : ''); 4493 4494 // Prepare Where 4495 if(vartrue($tparams['where'])) 4496 { 4497 $jwhere[] = $tparams['where']; 4498 } 4499 } 4500 4501 4502 //From 4503 $qry .= $tableSJoinArr ? ', '.implode(', ', $tableSJoinArr)." FROM ".$tableFrom : " FROM ".$tableFrom; 4504 4505 // Joins 4506 if(count($joins) > 0) 4507 { 4508 $qry .= "\n".implode("\n", $joins); 4509 } 4510 } 4511 else // default listQry 4512 { 4513 if(!empty($listQry)) 4514 { 4515 $qry = $this->parseCustomListQry($listQry); 4516 } 4517 elseif($this->sortField && $this->sortParent && !deftrue('e_DEBUG_TREESORT')) // automated 'tree' sorting. 4518 { 4519 // $qry = "SELECT SQL_CALC_FOUND_ROWS a. *, CASE WHEN a.".$this->sortParent." = 0 THEN a.".$this->sortField." ELSE b.".$this->sortField." + (( a.".$this->sortField.")/1000) END AS treesort FROM `#".$this->table."` AS a LEFT JOIN `#".$this->table."` AS b ON a.".$this->sortParent." = b.".$this->pid; 4520 $qry = $this->getParentChildQry(true); 4521 //$this->listOrder = '_treesort '; // .$this->sortField; 4522 // $this->orderStep = ($this->orderStep === 1) ? 100 : $this->orderStep; 4523 } 4524 else 4525 { 4526 $qry = "SELECT SQL_CALC_FOUND_ROWS ".$tableSFields." FROM ".$tableFrom; 4527 } 4528 4529 } 4530 4531 // group field - currently auto-added only if there are joins 4532 $groupField = ''; 4533 if($joins && $this->getPrimaryName()) 4534 { 4535 $groupField = $tablePath.$this->getPrimaryName(); 4536 } 4537 4538 // appended to GROUP BY when true. 4539 if(!empty($this->listGroup)) 4540 { 4541 $groupField = $this->listGroup; 4542 } 4543 4544 if($raw) 4545 { 4546 $rawData = array( 4547 'joinWhere' => $jwhere, 4548 'filter' => $filter, 4549 'listQrySql' => $this->listQrySql, 4550 'filterFrom' => $filterFrom, 4551 'search' => $searchQry, 4552 'tableFromName' => $tableFrom, 4553 ); 4554 4555 $orderField = $request->getQuery('field', $this->getDefaultOrderField()); 4556 4557 $rawData['tableFrom'] = $tableSFieldsArr; 4558 $rawData['joinsFrom'] = $tableSJoinArr; 4559 $rawData['joins'] = $joins; 4560 $rawData['groupField'] = $groupField; 4561 $rawData['orderField'] = isset($this->fields[$orderField]) ? $this->fields[$orderField]['__tableField'] : ''; 4562 $rawData['orderType'] = $request->getQuery('asc') === 'desc' ? 'DESC' : 'ASC'; 4563 $rawData['limitFrom'] = false === $forceFrom ? intval($request->getQuery('from', 0)) : intval($forceFrom); 4564 $rawData['limitTo'] = false === $forceTo ? intval($this->getPerPage()) : intval($forceTo); 4565 return $rawData; 4566 } 4567 4568 4569 // join where 4570 if(count($jwhere) > 0) 4571 { 4572 $searchQry[] = " (".implode(" AND ",$jwhere)." )"; 4573 } 4574 // filter where 4575 if(count($filter) > 0) 4576 { 4577 $searchQry[] = " ( ".implode(" OR ",$filter)." ) "; 4578 } 4579 4580 // more user added sql 4581 if(isset($this->listQrySql['db_where']) && $this->listQrySql['db_where']) 4582 { 4583 if(is_array($this->listQrySql['db_where'])) 4584 { 4585 $searchQry[] = implode(" AND ", $this->listQrySql['db_where']); 4586 } 4587 else 4588 { 4589 $searchQry[] = $this->listQrySql['db_where']; 4590 } 4591 } 4592 4593 4594 4595 // where query 4596 if(count($searchQry) > 0) 4597 { 4598 // add more where details on the fly via $this->listQrySql['db_where']; 4599 $qry .= (strripos($qry, 'where')==FALSE) ? " WHERE " : " AND "; // Allow 'where' in custom listqry 4600 $qry .= implode(" AND ", $searchQry); 4601 4602 // Disable tree (use flat list instead) when filters are applied 4603 // Implemented out of necessity under https://github.com/e107inc/e107/issues/3204 4604 // Horrible hack, but only needs this one line of additional code 4605 $this->getTreeModel()->setParam('sort_parent', null); 4606 } 4607 4608 // GROUP BY if needed 4609 if($groupField) 4610 { 4611 $qry .= ' GROUP BY '.$groupField; 4612 } 4613 4614 // only when no custom order is required 4615 if($this->listOrder && !$request->getQuery('field') && !$request->getQuery('asc')) 4616 { 4617 $qry .= ' ORDER BY '.$this->listOrder; 4618 } 4619 elseif(false !== $this->listOrder) 4620 { 4621 $orderField = $request->getQuery('field', $this->getDefaultOrderField()); 4622 $orderDef = (null === $request->getQuery('asc', null) ? $this->getDefaultOrder() : $request->getQuery('asc')); 4623 if(isset($this->fields[$orderField]) && strpos($this->listQry,'ORDER BY')==FALSE) //override ORDER using listQry (admin->sitelinks) 4624 { 4625 // no need of sanitize - it's found in field array 4626 $qry .= ' ORDER BY '.$this->fields[$orderField]['__tableField'].' '.(strtolower($orderDef) === 'desc' ? 'DESC' : 'ASC'); 4627 } 4628 } 4629 4630 if(isset($this->filterQry)) // custom query on filter. (see downloads plugin) 4631 { 4632 $qry = $this->filterQry; 4633 } 4634 4635 if($this->getPerPage() || false !== $forceTo) 4636 { 4637 $from = false === $forceFrom ? intval($request->getQuery('from', 0)) : intval($forceFrom); 4638 if(false === $forceTo) $forceTo = $this->getPerPage(); 4639 $qry .= ' LIMIT '.$from.', '.intval($forceTo); 4640 } 4641 4642 // Debug Filter Query. 4643 if(E107_DEBUG_LEVEL == E107_DBG_SQLQUERIES) 4644 { 4645 e107::getMessage()->addDebug('QRY='.str_replace('#', MPREFIX, $qry)); 4646 } 4647 // echo $qry.'<br />'; 4648 // print_a($this->fields); 4649 4650 $this->_log('listQry: '.str_replace('#', MPREFIX, $qry)); 4651 4652 return $qry; 4653 } 4654 4655 4656 /** 4657 * Return a Parent/Child SQL Query based on sortParent and sortField variables 4658 * 4659 * Note: Since 2018-01-28, the queries were replaced with pure PHP sorting. See: 4660 * https://github.com/e107inc/e107/issues/3015 4661 * 4662 * @param bool|false $orderby - include 'ORDER BY' in the qry. 4663 * @return string 4664 */ 4665 public function getParentChildQry($orderby=false) 4666 { 4667 return "SELECT SQL_CALC_FOUND_ROWS * FROM `#".$this->getTableName()."` "; 4668 } 4669 4670 4671 4672 4673 4674 4675 /** 4676 * Manage submit item 4677 * Note: $callbackBefore will break submission if returns false 4678 * 4679 * @param string $callbackBefore existing method from $this scope to be called before submit 4680 * @param string $callbackAfter existing method from $this scope to be called after successfull submit 4681 * @param string $noredirectAction passed to doAfterSubmit() 4682 * @return boolean 4683 */ 4684 protected function _manageSubmit($callbackBefore = '', $callbackAfter = '', $callbackError = '', $noredirectAction = '', $forceSave=false) 4685 { 4686 4687 $model = $this->getModel(); 4688 $old_data = $model->getData(); 4689 4690 $_posted = $this->getPosted(); 4691 $this->convertToData($_posted); 4692 4693 if($callbackBefore && method_exists($this, $callbackBefore)) 4694 { 4695 $data = $this->$callbackBefore($_posted, $old_data, $model->getId()); 4696 if(false === $data) 4697 { 4698 // we don't wanna loose posted data 4699 $model->setPostedData($_posted, null, false); 4700 return false; 4701 } 4702 if($data && is_array($data)) 4703 { 4704 // add to model data fields array if required 4705 foreach ($data as $f => $val) 4706 { 4707 if($this->getFieldAttr($f, 'data')) 4708 { 4709 $model->setDataField($f, $this->getFieldAttr($f, 'data')); 4710 } 4711 } 4712 $_posted = array_merge($_posted, $data); 4713 } 4714 } 4715 4716 // $model->addMessageDebug(print_a($_posted,true)); 4717 // $model->addMessageDebug(print_a($this,true)); 4718 4719 // - Autoincrement sortField on 'Create'. 4720 4721 4722 // Prevent parent being assigned as self. 4723 if(!empty($this->sortParent) && $this->getAction() === 'edit' && ($model->getId() == $_posted[$this->sortParent] ) ) 4724 { 4725 $vars = array( 4726 'x'=> $this->getFieldAttr($this->sortParent,'title'), 4727 'y'=> $this->getFieldAttr($this->pid,'title'), 4728 ); 4729 4730 $message = e107::getParser()->lanVars(LAN_UI_X_CANT_EQUAL_Y, $vars); 4731 $model->addMessageWarning($message); 4732 $model->setMessages(); 4733 $this->getUI()->addWarning($this->sortParent); 4734 return false; 4735 } 4736 4737 4738 4739 4740 if(($this->getAction() === 'create') && !empty($this->sortField) && empty($this->sortParent) && empty($_posted[$this->sortField]) ) 4741 { 4742 4743 $incVal = e107::getDb()->max($this->table, $this->sortField) + 1; 4744 $_posted[$this->sortField] = $incVal; 4745 // $model->addMessageInfo(print_a($_posted,true)); 4746 } 4747 4748 // Trigger Admin-ui event. 'pre' 4749 if($triggerName = $this->getEventTriggerName($_posted['etrigger_submit'])) // 'create' or 'update'; 4750 { 4751 $id = $model->getId(); 4752 $eventData = array('newData'=>$_posted,'oldData'=>$old_data,'id'=> $id); 4753 $model->addMessageDebug('Admin-ui Trigger fired: <b>'.$triggerName.'</b>'); 4754 $this->_log('Triggering Event: '.$triggerName. " (before)"); 4755 if(E107_DBG_ALLERRORS >0 ) 4756 { 4757 $model->addMessageDebug($triggerName.' data: '.print_a($eventData,true)); 4758 } 4759 4760 if($halt = e107::getEvent()->trigger($triggerName, $eventData)) 4761 { 4762 $model->setMessages(); 4763 return false; 4764 } 4765 } 4766 4767 4768 // Scenario I - use request owned POST data - toForm already executed 4769 $model->setPostedData($_posted, null, false) // insert() or update() dbInsert(); 4770 ->save(true, $forceSave); 4771 4772 4773 4774 // if(!empty($_POST)) 4775 { 4776 4777 } 4778 4779 // Scenario II - inner model sanitize 4780 //$this->getModel()->setPosted($this->convertToData($_POST, null, false, true); 4781 4782 // Take action based on use choice after success 4783 if(!$this->getModel()->hasError()) 4784 { 4785 // callback (if any) 4786 $new_data = $model->getData(); 4787 $id = $model->getId(); 4788 4789 e107::getAddonConfig('e_admin',null,'process', $this, $id); 4790 4791 // Trigger Admin-ui event. 'post' 4792 if($triggerName = $this->getEventTriggerName($_posted['etrigger_submit'],'after')) // 'created' or 'updated'; 4793 { 4794 unset($_posted['etrigger_submit'], $_posted['__after_submit_action'], $_posted['submit_value'], $_posted['e-token']); 4795 4796 $pid = $this->getPrimaryName(); 4797 $_posted[$pid] = $id; // add in the primary ID field. 4798 $eventData = array('newData'=>$_posted,'oldData'=>$old_data,'id'=> $id); // use $_posted as it may include unsaved data. 4799 $model->addMessageDebug('Admin-ui Trigger fired: <b>'.$triggerName.'</b>'); 4800 $this->_log('Triggering Event: '.$triggerName." (after)"); 4801 if(E107_DBG_ALLERRORS >0 ) 4802 { 4803 $model->addMessageDebug($triggerName.' data: '.print_a($eventData,true)); 4804 } 4805 e107::getEvent()->trigger($triggerName, $eventData); 4806 } 4807 4808 if($callbackAfter && method_exists($this, $callbackAfter)) 4809 { 4810 $this->$callbackAfter($new_data, $old_data, $id); 4811 } 4812 $model->setMessages(true); //FIX - move messages (and session messages) to the default stack 4813 $this->doAfterSubmit($model->getId(), $noredirectAction); 4814 return true; 4815 } 4816 elseif($callbackError && method_exists($this, $callbackError)) 4817 { 4818 // suppress messages if callback returns TRUE 4819 if(true !== $this->$callbackError($_posted, $old_data, $model->getId())) 4820 { 4821 // Copy model messages to the default message stack 4822 $model->setMessages(); 4823 } 4824 return false; 4825 } 4826 4827 // Copy model messages to the default message stack 4828 $model->setMessages(); 4829 return false; 4830 } 4831 4832 4833 /** 4834 * Return a custom event trigger name 4835 * @param null $type Usually 'Create' or 'Update' 4836 * @param string $when ' before or after 4837 * @return bool|string 4838 */ 4839 public function getEventTriggerName($type=null, $when='before') 4840 { 4841 $plug = $this->getEventName(); 4842 4843 if(empty($plug) || empty($type)) 4844 { 4845 return false; 4846 } 4847 4848 if($when === 'after') 4849 { 4850 $type .= 'd'; // ie. 'created' or 'updated'. 4851 } 4852 4853 return 'admin_'.strtolower($plug).'_'.strtolower($type); 4854 4855 } 4856} 4857 4858class e_admin_ui extends e_admin_controller_ui 4859{ 4860 4861 protected $fieldTypes = array(); 4862 protected $dataFields = array(); 4863 protected $fieldInputTypes = array(); 4864 protected $validationRules = array(); 4865 4866 protected $table; 4867 protected $pid; 4868 protected $listQry; 4869 protected $editQry; 4870 protected $sortField; 4871 protected $sortParent; 4872 protected $orderStep; 4873 protected $treePrefix; 4874 4875 4876 /** 4877 * Markup to be auto-inserted before List filter 4878 * @var string 4879 */ 4880 public $preFilterMarkup = ''; 4881 4882 /** 4883 * Markup to be auto-inserted after List filter 4884 * @var string 4885 */ 4886 public $postFilterMarkup = ''; 4887 4888 /** 4889 * Markup to be auto-inserted at the top of Create form 4890 * @var string 4891 */ 4892 public $headerCreateMarkup = ''; 4893 4894 /** 4895 * Markup to be auto-inserted at the bottom of Create form 4896 * @var string 4897 */ 4898 public $footerCreateMarkup = ''; 4899 4900 /** 4901 * Markup to be auto-inserted at the top of Update form 4902 * @var string 4903 */ 4904 public $headerUpdateMarkup = ''; 4905 4906 /** 4907 * Markup to be auto-inserted at the bottom of Update form 4908 * @var string 4909 */ 4910 public $footerUpdateMarkup = ''; 4911 4912 /** 4913 * Show confirm screen before (batch/single) delete 4914 * @var boolean 4915 */ 4916 public $deleteConfirmScreen = false; 4917 4918 /** 4919 * Confirm screen custom message 4920 * @var string 4921 */ 4922 public $deleteConfirmMessage = null; 4923 4924 4925 4926 /** 4927 * Constructor 4928 * @param e_admin_request $request 4929 * @param e_admin_response $response 4930 * @param array $params [optional] 4931 */ 4932 public function __construct($request, $response, $params = array()) 4933 { 4934 $this->setDefaultAction($request->getDefaultAction()); 4935 $params['enable_triggers'] = true; // override 4936 4937 parent::__construct($request, $response, $params); 4938 4939 if(!$this->pluginName) 4940 { 4941 $this->pluginName = 'core'; 4942 } 4943 4944 /* $ufieldpref = $this->getUserPref(); 4945 if($ufieldpref) 4946 { 4947 $this->fieldpref = $ufieldpref; 4948 }*/ 4949 4950 $this->addTitle($this->pluginTitle, true)->parseAliases(); 4951 4952 $this->initAdminAddons(); 4953 4954 4955 if($help = $this->renderHelp()) 4956 { 4957 if(!empty($help)) 4958 { 4959 e107::setRegistry('core/e107/adminui/help',$help); 4960 } 4961 } 4962 4963 4964 } 4965 4966 4967 private function initAdminAddons() 4968 { 4969 $tmp = e107::getAddonConfig('e_admin', null, 'config', $this); 4970 4971 if(empty($tmp)) 4972 { 4973 return; 4974 } 4975 4976 $opts = null; 4977 4978 foreach($tmp as $plug=>$config) 4979 { 4980 4981 $form = e107::getAddon($plug, 'e_admin', $plug."_admin_form"); // class | false. 4982 4983 if(!empty($config['fields'])) 4984 { 4985 if(!empty($this->fields['options'])) 4986 { 4987 $opts = $this->fields['options']; 4988 unset($this->fields['options']); 4989 } 4990 4991 foreach($config['fields'] as $k=>$v) 4992 { 4993 $v['data'] = false; // disable data-saving to db table. . 4994 4995 $fieldName = 'x_'.$plug.'_'.$k; 4996 e107::getDebug()->log($fieldName." initiated by ".$plug); 4997 4998 if($v['type'] === 'method' && method_exists($form,$fieldName)) 4999 { 5000 $v['method'] = $plug."_admin_form::".$fieldName; 5001 //echo "Found method ".$fieldName." in ".$plug."_menu_form"; 5002 //echo $form->$fieldName(); 5003 } 5004 5005 5006 $this->fields[$fieldName] = $v; // ie. x_plugin_key 5007 5008 } 5009 5010 if(!empty($opts)) // move options field to the end. 5011 { 5012 $this->fields['options'] = $opts; 5013 } 5014 } 5015 5016 if(!empty($config['batchOptions'])) 5017 { 5018 $opts = array(); 5019 foreach($config['batchOptions'] as $k=>$v) 5020 { 5021 $fieldName = 'batch_'.$plug.'_'.$k; 5022 5023 $opts[$fieldName] = $v; // ie. x_plugin_key 5024 5025 } 5026 5027 $batchCat = deftrue('LAN_PLUGIN_'.strtoupper($plug).'_NAME', $plug); 5028 $this->batchOptions[$batchCat] = $opts; 5029 5030 } 5031 5032 if(!empty($config['tabs'])) 5033 { 5034 foreach($config['tabs'] as $t=>$tb) 5035 { 5036 $this->tabs[$t] = $tb; 5037 } 5038 } 5039 5040 5041 } 5042 5043 5044 5045 5046 } 5047 5048 5049 5050 /** 5051 * Catch fieldpref submit 5052 * @return none 5053 */ 5054 public function ListEcolumnsTrigger() 5055 { 5056 $this->setTriggersEnabled(false); //disable further triggering 5057 parent::manageColumns(); 5058 } 5059 5060 5061 /** 5062 * Detect if a batch function has been fired. 5063 * @param $batchKey 5064 * @return bool 5065 */ 5066 public function batchTriggered($batchKey) 5067 { 5068 return (!empty($_POST['e__execute_batch']) && (varset($_POST['etrigger_batch']) == $batchKey)); 5069 } 5070 5071 5072 5073 /** 5074 * Catch batch submit 5075 * @param string $batch_trigger 5076 * @return none 5077 */ 5078 public function ListBatchTrigger($batch_trigger) 5079 { 5080 $this->setPosted('etrigger_batch', null); 5081 5082 if($this->getPosted('etrigger_cancel')) 5083 { 5084 $this->setPosted(array()); 5085 return; // always break on cancel! 5086 } 5087 $this->deleteConfirmScreen = true; // Confirm screen ALWAYS enabled when multi-deleting! 5088 5089 // proceed ONLY if there is no other trigger, except delete confirmation 5090 if($batch_trigger && !$this->hasTrigger(array('etrigger_delete_confirm'))) $this->_handleListBatch($batch_trigger); 5091 } 5092 5093 /** 5094 * Catch batch submit 5095 * @param string $batch_trigger 5096 * @return none 5097 */ 5098 public function GridBatchTrigger($batch_trigger) 5099 { 5100 $this->setPosted('etrigger_batch', null); 5101 5102 if($this->getPosted('etrigger_cancel')) 5103 { 5104 $this->setPosted(array()); 5105 return; // always break on cancel! 5106 } 5107 $this->deleteConfirmScreen = true; // Confirm screen ALWAYS enabled when multi-deleting! 5108 5109 // proceed ONLY if there is no other trigger, except delete confirmation 5110 if($batch_trigger && !$this->hasTrigger(array('etrigger_delete_confirm'))) $this->_handleListBatch($batch_trigger); 5111 } 5112 5113 /** 5114 * Batch delete trigger 5115 * @param array $selected 5116 * @return void 5117 */ 5118 protected function handleListDeleteBatch($selected) 5119 { 5120 5121 $tp = e107::getParser(); 5122 5123 if(!$this->getBatchDelete()) 5124 { 5125 e107::getMessage()->add(LAN_UI_BATCHDEL_ERROR, E_MESSAGE_WARNING); 5126 return; 5127 } 5128 if($this->deleteConfirmScreen) 5129 { 5130 if(!$this->getPosted('etrigger_delete_confirm')) 5131 { 5132 // ListPage will show up confirmation screen 5133 $this->setPosted('delete_confirm_value', implode(',', $selected)); 5134 return; 5135 } 5136 else 5137 { 5138 // already confirmed, resurrect selected values 5139 $selected = explode(',', $this->getPosted('delete_confirm_value')); 5140 foreach ($selected as $i => $_sel) 5141 { 5142 $selected[$i] = preg_replace('/[^\w\-:.]/', '', $_sel); 5143 } 5144 } 5145 } 5146 5147 // delete one by one - more control, less performance 5148 // pass afterDelete() callback to tree delete method 5149 $set_messages = true; 5150 $delcount = 0; 5151 $nfcount = 0; 5152 foreach ($selected as $id) 5153 { 5154 $data = array(); 5155 $model = $this->getTreeModel()->getNode($id); 5156 if($model) 5157 { 5158 $data = $model->getData(); 5159 if($this->beforeDelete($data, $id)) 5160 { 5161 $check = $this->getTreeModel()->delete($id); 5162 if($check) $delcount++; 5163 if(!$this->afterDelete($data, $id, $check)) 5164 { 5165 $set_messages = false; 5166 } 5167 } 5168 } 5169 else 5170 { 5171 $set_messages = true; 5172 $nfcount++; 5173 } 5174 } 5175 5176 //$this->getTreeModel()->delete($selected); 5177 if($set_messages) 5178 { 5179 $this->getTreeModel()->setMessages(); 5180 // FIXME lan 5181 if($delcount) e107::getMessage()->addSuccess($tp->lanVars(LAN_UI_DELETED, $delcount, true)); 5182 if($nfcount) e107::getMessage()->addError($tp->lanVars(LAN_UI_DELETED_FAILED, $nfcount,true)); 5183 } 5184 5185 //$this->redirect(); 5186 } 5187 5188 /** 5189 * Batch copy trigger 5190 * @param array $selected 5191 * @return void 5192 */ 5193 protected function handleListCopyBatch($selected) 5194 { 5195 // Batch Copy 5196 5197 $res = $this->getTreeModel()->copy($selected); 5198 // callback 5199 $this->afterCopy($res, $selected); 5200 // move messages to default stack 5201 $this->getTreeModel()->setMessages(); 5202 // send messages to session 5203 e107::getMessage()->moveToSession(); 5204 // redirect 5205 $this->redirect(); 5206 } 5207 5208 5209 /** 5210 * Batch Export trigger 5211 * @param array $selected 5212 * @return void 5213 */ 5214 protected function handleListExportBatch($selected) 5215 { 5216 // Batch Copy 5217 $res = $this->getTreeModel()->export($selected); 5218 // callback 5219 // $this->afterCopy($res, $selected); 5220 // move messages to default stack 5221 $this->getTreeModel()->setMessages(); 5222 // send messages to session 5223 e107::getMessage()->moveToSession(); 5224 // redirect 5225 $this->redirect(); 5226 } 5227 5228 5229 /** 5230 * Batch Export trigger 5231 * @param array $selected 5232 * @return void 5233 */ 5234 protected function handleListSefgenBatch($selected, $field, $value) 5235 { 5236 5237 $tree = $this->getTreeModel(); 5238 $c= 0; 5239 foreach($selected as $id) 5240 { 5241 if(!$tree->hasNode($id)) 5242 { 5243 e107::getMessage()->addError('Item #ID '.htmlspecialchars($id).' not found.'); 5244 continue; 5245 } 5246 5247 $model = $tree->getNode($id); 5248 5249 $name = $model->get($value); 5250 5251 $sef = eHelper::title2sef($name,'dashl'); 5252 5253 5254 5255 5256 5257 $model->set($field, $sef); 5258 5259 5260 $model->save(); 5261 5262 $data = $model->getData(); 5263 5264 if($model->isModified()) 5265 { 5266 $this->getModel()->setData($data)->save(false,true); 5267 $c++; 5268 } 5269 } 5270 5271 5272 5273 $caption = e107::getParser()->lanVars(LAN_UI_BATCH_BOOL_SUCCESS, $c, true); 5274 e107::getMessage()->addSuccess($caption); 5275 5276 // e107::getMessage()->moveToSession(); 5277 // redirect 5278 // $this->redirect(); 5279 } 5280 5281 5282 5283 /** 5284 * Batch URL trigger 5285 * @param array $selected 5286 * @return void 5287 */ 5288 protected function handleListUrlBatch($selected) 5289 { 5290 if($this->_add2nav($selected)) 5291 { 5292 e107::getMessage()->moveToSession(); 5293 $this->redirect(); 5294 } 5295 } 5296 5297 5298 /** TODO 5299 * Batch Featurebox Transfer 5300 * @param array $selected 5301 * @return void 5302 */ 5303 protected function handleListFeatureboxBatch($selected) 5304 { 5305 if($this->_add2featurebox($selected)) 5306 { 5307 e107::getMessage()->moveToSession(); 5308 $this->redirect(); 5309 } 5310 } 5311 5312 protected function _add2nav($selected) 5313 { 5314 if(empty($selected)) return false;// TODO warning message 5315 5316 if(!is_array($selected)) $selected = array($selected); 5317 5318 $sql = e107::getDb(); 5319 $urlData = $this->getUrl(); 5320 $allData = $this->getTreeModel()->url($selected, array('sc' => true), true); 5321 5322 e107::getMessage()->addDebug('Using Url Route:'.$urlData['route']); 5323 5324 $scount = 0; 5325 foreach($allData as $id => $data) 5326 { 5327 $name = $data['name']; 5328 $desc = $data['description']; 5329 5330 $link = $data['url']; 5331 5332 $link = str_replace('{e_BASE}', "", $link); // TODO temporary here, discuss 5333 5334 // _FIELD_TYPES auto created inside mysql handler now 5335 $linkArray = array( 5336 'link_name' => $name, 5337 'link_url' => $link, 5338 'link_description' => e107::getParser()->toDB($desc), // retrieved field type is string, we might need todb here 5339 'link_button' => '', 5340 'link_category' => 255, // Using an unassigned template rather than inactive link-class, since other inactive links may already exist. 5341 'link_order' => 0, 5342 'link_parent' => 0, 5343 'link_open' => '', 5344 'link_class' => 0, 5345 'link_sefurl' => e107::getParser()->toDB($urlData['route'].'?'.$id), 5346 ); 5347 5348 $res = $sql->insert('links', $linkArray); 5349 5350 if($res !== FALSE) 5351 { 5352 e107::getMessage()->addSuccess(LAN_CREATED.": ".LAN_SITELINK.": ".($name ? $name : 'n/a')); 5353 $scount++; 5354 } 5355 else 5356 { 5357 if($sql->getLastErrorNumber()) 5358 { 5359 e107::getMessage()->addError(LAN_CREATED_FAILED.": ".LAN_SITELINK.": ".$name.": ".LAN_SQL_ERROR); 5360 e107::getMessage()->addDebug('SQL Link Creation Error #'.$sql->getLastErrorNumber().': '.$sql->getLastErrorText()); 5361 } 5362 else 5363 { 5364 e107::getMessage()->addError(LAN_CREATED_FAILED.": ".LAN_SITELINK.": ".$name.": ".LAN_UNKNOWN_ERROR);//Unknown Error 5365 } 5366 } 5367 5368 } 5369 5370 if($scount > 0) 5371 { 5372 e107::getMessage()->addSuccess(LAN_CREATED." (".$scount.") ".ADLAN_138); 5373 e107::getMessage()->addSuccess("<a class='btn btn-small btn-primary' href='".e_ADMIN_ABS."links.php?searchquery=&filter_options=link_category__255'>".LAN_CONFIGURE." ".ADLAN_138."</a>"); 5374 return $scount; 5375 } 5376 5377 return false; 5378 5379 } 5380 5381 protected function _add2featurebox($selected) 5382 { 5383 // FIX - don't allow if plugin not installed 5384 if(!e107::isInstalled('featurebox')) 5385 { 5386 return false; 5387 } 5388 5389 if(empty($selected)) return false;// TODO warning message 5390 5391 if(!is_array($selected)) $selected = array($selected); 5392 5393 $sql = e107::getDb(); 5394 $tree = $this->getTreeModel(); 5395 $urlData = $this->getTreeModel()->url($selected, array('sc' => true), false); 5396 $data = $this->featurebox; 5397 5398 $scount = 0; 5399 foreach($selected as $id) 5400 { 5401 if(!$tree->hasNode($id)) 5402 { 5403 e107::getMessage()->addError('Item #ID '.htmlspecialchars($id).' not found.'); 5404 continue; // TODO message 5405 } 5406 5407 $model = $tree->getNode($id); 5408 if($data['url'] === true) 5409 { 5410 $url = $urlData[$id]; 5411 } 5412 else $url = $model->get($data['url']); 5413 $name = $model->get($data['name']); 5414 5415 $category = e107::getDb()->retrieve('featurebox_category', 'fb_category_id', "fb_category_template='unassigned'"); 5416 5417 $fbArray = array ( 5418 'fb_title' => $name, 5419 'fb_text' => $model->get($data['description']), 5420 'fb_image' => vartrue($data['image']) ? $model->get($data['image']) : '', 5421 'fb_imageurl' => $url, 5422 'fb_class' => isset($data['visibility']) && $data['visibility'] !== false ? $model->get($data['visibility']) : e_UC_ADMIN, 5423 'fb_template' => 'default', 5424 'fb_category' => $category, // TODO popup - choose category 5425 'fb_order' => $scount, 5426 ); 5427 5428 $res = $sql->insert('featurebox', $fbArray); 5429 5430 if($res !== FALSE) 5431 { 5432 e107::getMessage()->addSuccess(LAN_CREATED.": ".LAN_PLUGIN_FEATUREBOX_NAME.": ".($name ? $name : 'n/a')); 5433 $scount++; 5434 } 5435 else 5436 { 5437 if($sql->getLastErrorNumber()) 5438 { 5439 e107::getMessage()->addError(LAN_CREATED_FAILED.": ".LAN_PLUGIN_FEATUREBOX_NAME.": ".$name.": ".LAN_SQL_ERROR); 5440 e107::getMessage()->addDebug('SQL Featurebox Creation Error #'.$sql->getLastErrorNumber().': '.$sql->getLastErrorText()); 5441 } 5442 else 5443 { 5444 e107::getMessage()->addError(LAN_CREATED_FAILED.": ".$name.": ".LAN_UNKNOWN_ERROR); 5445 } 5446 } 5447 } 5448 5449 if($scount > 0) 5450 { 5451 e107::getMessage()->addSuccess(LAN_CREATED." (".$scount.") ".LAN_PLUGIN_FEATUREBOX_NAME); 5452 e107::getMessage()->addSuccess("<a class='btn btn-small btn-primary' href='".e_PLUGIN_ABS."featurebox/admin_config.php?searchquery=&filter_options=fb_category__{$category}'".LAN_CONFIGURE." ".LAN_PLUGIN_FEATUREBOX_NAME."</a>"); 5453 return $scount; 5454 } 5455 5456 return false; 5457 5458 } 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 /** 5474 * Batch boolean trigger 5475 * @param array $selected 5476 * @return void 5477 */ 5478 protected function handleListBoolBatch($selected, $field, $value) 5479 { 5480 $cnt = $this->getTreeModel()->batchUpdate($field, $value, $selected, $value, false); 5481 if($cnt) 5482 { 5483 $caption = e107::getParser()->lanVars(LAN_UI_BATCH_BOOL_SUCCESS, $cnt, true); 5484 $this->getTreeModel()->addMessageSuccess($caption); 5485 } 5486 $this->getTreeModel()->setMessages(); 5487 } 5488 5489 /** 5490 * Batch boolean reverse trigger 5491 * @param array $selected 5492 * @return void 5493 */ 5494 protected function handleListBoolreverseBatch($selected, $field) 5495 { 5496 $tree = $this->getTreeModel(); 5497 $cnt = $tree->batchUpdate($field, "1-{$field}", $selected, null, false); 5498 if($cnt) 5499 { 5500 $caption = e107::getParser()->lanVars(LAN_UI_BATCH_REVERSED_SUCCESS, $cnt, true); 5501 $tree->addMessageSuccess($caption); 5502 //sync models 5503 $tree->loadBatch(true); 5504 } 5505 $this->getTreeModel()->setMessages(); 5506 } 5507 5508 public function handleCommaBatch($selected, $field, $value, $type) 5509 { 5510 $tree = $this->getTreeModel(); 5511 $cnt = $rcnt = 0; 5512 $value = e107::getParser()->toDB($value); 5513 5514 switch ($type) 5515 { 5516 case 'attach': 5517 case 'deattach': 5518 $this->_setModel(); 5519 foreach ($selected as $key => $id) 5520 { 5521 $node = $tree->getNode($id); 5522 if(!$node) continue; 5523 $val = $node->get($field); 5524 5525 if(empty($val)) $val = array(); 5526 elseif(!is_array($val)) $val = explode(',', $val); 5527 5528 if($type === 'deattach') 5529 { 5530 $search = array_search($value, $val); 5531 if(false === $search) continue; 5532 unset($val[$search]); 5533 sort($val); 5534 $val = implode(',', $val); 5535 $node->set($field, $val); 5536 $check = $this->getModel()->setData($node->getData())->save(false, true); 5537 5538 if(false === $check) $this->getModel()->setMessages(); 5539 else $rcnt++; 5540 continue; 5541 } 5542 5543 // attach it 5544 if(false === in_array($value, $val)) 5545 { 5546 $val[] = $value; 5547 sort($val); 5548 $val = implode(',', array_unique($val)); 5549 $node->set($field, $val); 5550 $check = $this->getModel()->setData($node->getData())->save(false, true); 5551 if(false === $check) $this->getModel()->setMessages(); 5552 else $cnt++; 5553 } 5554 } 5555 $this->_model = null; 5556 break; 5557 5558 case 'addAll': 5559 if(!empty($value)) 5560 { 5561 if(is_array($value)) 5562 { 5563 sort($value); 5564 $value = implode(',', array_map('trim', $value)); 5565 } 5566 5567 $cnt = $this->getTreeModel()->batchUpdate($field, $value, $selected, true, true); 5568 } 5569 else 5570 { 5571 $this->getTreeModel()->addMessageWarning(LAN_UPDATED_FAILED)->setMessages();//"Comma list is empty, aborting." 5572 $this->getTreeModel()->addMessageDebug(LAN_UPDATED_FAILED.": Comma list is empty, aborting.")->setMessages(); 5573 } 5574 break; 5575 5576 case 'clearAll': 5577 $allowed = !is_array($value) ? explode(',', $value) : $value; 5578 if(!$allowed) 5579 { 5580 $rcnt = $this->getTreeModel()->batchUpdate($field, '', $selected, '', true); 5581 } 5582 else 5583 { 5584 $this->_setModel(); 5585 foreach ($selected as $key => $id) 5586 { 5587 $node = $tree->getNode($id); 5588 if(!$node) continue; 5589 5590 $val = $node->get($field); 5591 5592 // nothing to do 5593 if(empty($val)) break; 5594 elseif(!is_array($val)) $val = explode(',', $val); 5595 5596 // remove only allowed, see userclass 5597 foreach ($val as $_k => $_v) 5598 { 5599 if(in_array($_v, $allowed)) 5600 { 5601 unset($val[$_k]); 5602 } 5603 } 5604 5605 sort($val); 5606 $val = !empty($val) ? implode(',', $val) : ''; 5607 $node->set($field, $val); 5608 $check = $this->getModel()->setData($node->getData())->save(false, true); 5609 5610 if(false === $check) $this->getModel()->setMessages(); 5611 else $rcnt++; 5612 } 5613 $this->_model = null; 5614 } 5615 5616 // format for proper message 5617 $value = implode(',', $allowed); 5618 break; 5619 } 5620 5621 if($cnt) 5622 { 5623 $vttl = $this->getUI()->renderValue($field, $value, $this->getFieldAttr($field)); 5624 $caption = e107::getParser()->lanVars(LAN_UI_BATCH_UPDATE_SUCCESS, array('x'=>$vttl, 'y'=>$cnt), true); 5625 $this->getTreeModel()->addMessageSuccess($caption); 5626 } 5627 elseif($rcnt) 5628 { 5629 $vttl = $this->getUI()->renderValue($field, $value, $this->getFieldAttr($field)); 5630 $caption = e107::getParser()->lanVars(LAN_UI_BATCH_DEATTACH_SUCCESS, array('x'=>$vttl, 'y'=>$cnt), true); 5631 $this->getTreeModel()->addMessageSuccess($caption); 5632 } 5633 $this->getTreeModel()->setMessages(); 5634 } 5635 5636 5637 /** 5638 * Method to generate "Search in Field" query. 5639 * @param $selected 5640 * @return string 5641 */ 5642 protected function handleListSearchfieldFilter($selected) 5643 { 5644 $string = $this->getQuery('searchquery'); 5645 5646 5647 5648 if(empty($string)) 5649 { 5650 return null; 5651 } 5652 5653 return $selected. " LIKE '%".e107::getParser()->toDB($string)."%' "; // array($selected, $this->getQuery('searchquery')); 5654 } 5655 5656 /** 5657 * Batch default (field) trigger 5658 * @param array $selected 5659 * @return void 5660 */ 5661 protected function handleListBatch($selected, $field, $value) 5662 { 5663 // special exceptions 5664 5665 if($value === '#delete') // see admin->users 5666 { 5667 $val = "''"; 5668 $value = "(empty)"; 5669 } 5670 elseif($value === "#null") 5671 { 5672 $val = null; 5673 $value = "(empty)"; 5674 } 5675 else 5676 { 5677 $val = "'".$value."'"; 5678 } 5679 5680 if($field === 'options') // reserved field type. see: admin -> media-manager - batch rotate image. 5681 { 5682 return; 5683 } 5684 5685 5686 5687 5688 $cnt = $this->getTreeModel()->batchUpdate($field, $val, $selected, true, false); 5689 if($cnt) 5690 { 5691 $vttl = $this->getUI()->renderValue($field, $value, $this->getFieldAttr($field)); 5692 $msg = e107::getParser()->lanVars(LAN_UI_BATCH_UPDATE_SUCCESS, array('x' => $vttl, 'y' => $cnt), true); 5693 $this->getTreeModel()->addMessageSuccess($msg); 5694 // force reload the collection from DB, fix some issues as 'observer' is executed before the batch handler 5695 $this->getTreeModel()->setParam('db_query', $this->_modifyListQry(false, false, false, false, $this->listQry))->loadBatch(true); 5696 } 5697 $this->getTreeModel()->setMessages(); 5698 return $cnt; 5699 } 5700 5701 /** 5702 * Catch delete submit 5703 * @param string $batch_trigger 5704 * @return none 5705 */ 5706 public function ListDeleteTrigger($posted) 5707 { 5708 if($this->getPosted('etrigger_cancel')) 5709 { 5710 $this->setPosted(array()); 5711 return; // always break on cancel! 5712 } 5713 5714 $id = intval(key($posted)); 5715 if($this->deleteConfirmScreen && !$this->getPosted('etrigger_delete_confirm')) 5716 { 5717 // forward data to delete confirm screen 5718 $this->setPosted('delete_confirm_value', $id); 5719 return; // User confirmation expected 5720 } 5721 5722 $this->setTriggersEnabled(false); 5723 $data = array(); 5724 $model = $this->getTreeModel()->getNode($id); //FIXME - this has issues with being on a page other than the 1st. 5725 if($model) 5726 { 5727 $data = $model->getData(); 5728 if($this->beforeDelete($data, $id)) 5729 { 5730 5731 $eventData = array('oldData'=>$data,'id'=> $id); 5732 5733 if($triggerName = $this->getEventTriggerName('delete')) // trigger for before. 5734 { 5735 5736 if(E107_DBG_ALLERRORS >0 ) 5737 { 5738 $this->getTreeModel()->addMessageDebug('Admin-ui Trigger fired: <b>'.$triggerName.'</b> with data '.print_a($eventData,true)); 5739 } 5740 5741 if($halt = e107::getEvent()->trigger($triggerName, $eventData)) 5742 { 5743 $this->getTreeModel()->setMessages(); 5744 return; 5745 } 5746 } 5747 5748 $check = $this->getTreeModel()->delete($id); 5749 5750 if($this->afterDelete($data, $id, $check)) 5751 { 5752 if($triggerName = $this->getEventTriggerName('deleted')) // trigger for after. 5753 { 5754 if(E107_DBG_ALLERRORS > 0) 5755 { 5756 $this->getTreeModel()->addMessageDebug('Admin-ui Trigger fired: <b>'.$triggerName.'</b>'); //FIXME - Why doesn't this display? 5757 } 5758 e107::getEvent()->trigger($triggerName, $eventData); 5759 } 5760 5761 $this->getTreeModel()->setMessages(); 5762 } 5763 } 5764 else 5765 { 5766 $this->getTreeModel()->setMessages();// errors 5767 } 5768 } 5769 else //FIXME - this is a fall-back for the BUG which causes model to fail on all list pages other than the 1st 5770 { 5771 //echo "Couldn't get Node for ID: ".$id; 5772 // exit; 5773 e107::getMessage()->addDebug('Model Failure Fallback in use!! ID: '.$id.' file: '.__FILE__. " line: ".__LINE__ ,'default',true); 5774 $check = $this->getTreeModel()->delete($id); 5775 return; 5776 } 5777 } 5778 5779 /** 5780 * User defined pre-delete logic 5781 */ 5782 public function beforeDelete($data, $id) 5783 { 5784 return true; 5785 } 5786 5787 /** 5788 * User defined after-delete logic 5789 */ 5790 public function afterDelete($deleted_data, $id, $deleted_check) 5791 { 5792 return true; 5793 } 5794 5795 /** 5796 * List action header 5797 * @return void 5798 */ 5799 public function ListHeader() 5800 { 5801 //e107::js('core','core/tabs.js','prototype'); 5802 //e107::js('core','core/admin.js','prototype'); 5803 } 5804 5805 /** 5806 * List action observer 5807 * @return void 5808 */ 5809 public function ListObserver() 5810 { 5811 if($ufieldpref = $this->getUserPref()) 5812 { 5813 $this->fieldpref = $ufieldpref; 5814 } 5815 5816 $table = $this->getTableName(); 5817 if(empty($table)) 5818 { 5819 return; 5820 } 5821 5822 $this->getTreeModel()->setParam('db_query', $this->_modifyListQry(false, false, false, false, $this->listQry))->loadBatch(); 5823 5824 $this->addTitle(); 5825 5826 if($this->getQuery('filter_options')) 5827 { 5828 // var_dump($this); 5829 // $this->addTitle("to-do"); // display filter option when active. 5830 } 5831 5832 } 5833 5834 /** 5835 * Grid action observer 5836 */ 5837 public function GridObserver() 5838 { 5839 5840 $table = $this->getTableName(); 5841 if(empty($table)) 5842 { 5843 return; 5844 } 5845 $this->getTreeModel()->setParam('db_query', $this->_modifyListQry(false, false, false, false, $this->listQry))->loadBatch(); 5846 } 5847 5848 /** 5849 * Filter response ajax page 5850 * @return string 5851 */ 5852 public function FilterAjaxPage() 5853 { 5854 return $this->renderAjaxFilterResponse($this->listQry); //listQry will be used only if available 5855 } 5856 5857 /** 5858 * Inline edit action 5859 * @return void 5860 */ 5861 public function InlineAjaxPage() 5862 { 5863 $this->logajax("Inline Ajax Triggered"); 5864 5865 $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'); 5866 if(!vartrue($_POST['name']) || !vartrue($this->fields[$_POST['name']])) 5867 { 5868 header($protocol.': 404 Not Found', true, 404); 5869 header("Status: 404 Not Found", true, 404); 5870 echo LAN_FIELD.": ".$this->fields[$_POST['name']].": ".LAN_NOT_FOUND; // Field: x: not found! 5871 $this->logajax('Field not found'); 5872 return; 5873 } 5874 5875 $_name = $_POST['name']; 5876 $_value = $_POST['value']; 5877 $_token = $_POST['token']; 5878 5879 $parms = $this->fields[$_name]['readParms'] ? $this->fields[$_name]['readParms'] : ''; 5880 if(!is_array($parms)) parse_str($parms, $parms); 5881 if(!empty($parms['editable'])) $this->fields[$_name]['inline'] = true; 5882 5883 if(!empty($this->fields[$_name]['noedit']) || !empty($this->fields[$_name]['nolist']) || empty($this->fields[$_name]['inline']) || empty($_token) || !password_verify(session_id(),$_token)) 5884 { 5885 header($protocol.': 403 Forbidden', true, 403); 5886 header("Status: 403 Forbidden", true, 403); 5887 echo ADLAN_86; //Forbidden 5888 5889 $result = var_export($this->fields[$_name], true); 5890 5891 $problem = array(); 5892 $problem['noedit'] = !empty($this->fields[$_name]['noedit']) ? 'yes' : 'no'; 5893 $problem['nolist'] = !empty($this->fields[$_name]['nolist']) ? 'yes' : 'no'; 5894 $problem['inline'] = empty($this->fields[$_name]['inline']) ? 'yes' : 'no'; 5895 $problem['token'] = empty($_token) ? 'yes' : 'no'; 5896 $problem['password'] = !password_verify(session_id(),$_token) ? 'yes' : 'no'; 5897 5898 $result .= "\nForbidden Caused by: ".print_r($problem,true); 5899 $this->logajax("Forbidden\nAction:".$this->getAction()."\nField (".$_name."):\n".$result); 5900 return; 5901 } 5902 5903 5904 5905 5906 5907 $model = $this->getModel()->load($this->getId()); 5908 $_POST = array(); //reset post 5909 $_POST[$_name] = $_value; // set current field only 5910 $_POST['etrigger_submit'] = 'update'; // needed for event trigger 5911 5912 // print_r($_POST); 5913 5914 // generic handler - same as regular edit form submit 5915 5916 $this->convertToData($_POST); 5917 5918 $model->setPostedData($_POST, null, false); 5919 $model->setParam('validateAvailable', true); // new param to control validate of available data only, reset on validate event 5920 // Do not update here! Because $old_data and $new_data will be the same in afterUpdate() methods. 5921 // Data will be saved in _manageSubmit() method. 5922 // $model->update(true); 5923 5924 if($model->hasError()) 5925 { 5926 // using 400 5927 header($protocol.': 400 Bad Request', true, 400); 5928 header("Status: 400 Bad Request", true, 400); 5929 $this->logajax("Bad Request"); 5930 // DEBUG e107::getMessage()->addError('Error test.', $model->getMessageStackName())->addError('Another error test.', $model->getMessageStackName()); 5931 5932 5933 if(E107_DEBUG_LEVEL) $message = e107::getMessage()->get('debug', $model->getMessageStackName(), true); 5934 else $message = e107::getMessage()->get('error', $model->getMessageStackName(), true); 5935 5936 if(!empty($message)) echo implode(' ', $message); 5937 $this->logajax(implode(' ', $message)); 5938 return; 5939 } 5940 5941 //TODO ? afterInline trigger? 5942 $res = $this->_manageSubmit('beforeUpdate', 'afterUpdate', 'onUpdateError', 'edit'); 5943 } 5944 5945 // Temporary - but useful. :-) 5946 public function logajax($message) 5947 { 5948 if(e_DEBUG !== true) 5949 { 5950 return; 5951 } 5952 5953 $message = date('r')."\n".$message."\n"; 5954 $message .= "\n_POST\n"; 5955 $message .= print_r($_POST,true); 5956 $message .= "\n_GET\n"; 5957 $message .= print_r($_GET,true); 5958 5959 $message .= "---------------"; 5960 5961 file_put_contents(e_LOG.'uiAjaxResponseInline.log', $message."\n\n", FILE_APPEND); 5962 } 5963 5964 5965 /** 5966 * Drag-n-Drop sort action 5967 * @return void 5968 */ 5969 public function SortAjaxPage() 5970 { 5971 if(!isset($_POST['all']) || empty($_POST['all'])) 5972 { 5973 return; 5974 } 5975 if(!$this->sortField) 5976 { 5977 echo 'Missing sort field value'; 5978 return; 5979 } 5980 5981 if(!empty($this->sortParent)) // Force 100 positions for child when sorting with parent/child. 5982 { 5983 $this->orderStep = 100; 5984 } 5985 5986 5987 $sql = e107::getDb(); 5988 $step = $this->orderStep ? intval($this->orderStep) : 1; 5989 $from = !empty($_GET['from']) ? (int) $_GET['from'] * $step : $step; 5990 5991 $c = $from; 5992 $updated = array(); 5993 5994 foreach($_POST['all'] as $row) 5995 { 5996 5997 list($tmp,$id) = explode("-", $row, 2); 5998 $id = preg_replace('/[^\w\-:.]/', '', $id); 5999 if(!is_numeric($id)) $id = "'{$id}'"; 6000 if($sql->update($this->table, $this->sortField." = {$c} WHERE ".$this->pid." = ".$id)!==false) 6001 { 6002 $updated[] = "#".$id." -- ".$this->sortField." = ".$c; 6003 } 6004 6005 // echo($sql->getLastQuery()."\n"); 6006 $c += $step; 6007 6008 } 6009 6010 6011 if(!empty($this->sortParent) && !empty($this->sortField) ) 6012 { 6013 return null; 6014 } 6015 6016// file_put_contents(e_LOG."sortAjax.log", print_r($updated,true)); 6017 6018 // Increment every other record after the current page of records. 6019 // $changed = (intval($_POST['neworder']) * $step) + $from ; 6020 $changed = $c - $step; 6021 $qry = "UPDATE `#".$this->table."` e, (SELECT @n := ".($changed).") m SET e.".$this->sortField." = @n := @n + ".$step." WHERE ".$this->sortField." > ".($changed); 6022 6023 $result = $sql->gen($qry); 6024 6025 6026 // ------------ Fix Child Order when parent is used. ---------------- 6027/* 6028 if(!empty($this->sortParent) && !empty($this->sortField) ) // Make sure there is space for at least 99 6029 { 6030 $parent = array(); 6031 6032 $data2 = $sql->retrieve($this->table,$this->pid.','.$this->sortField,$this->sortParent .' = 0',true); 6033 foreach($data2 as $val) 6034 { 6035 $id = $val[$this->pid]; 6036 $parent[$id] = $val[$this->sortField]; 6037 6038 } 6039 6040 $previous = 0; 6041 6042 $data = $sql->retrieve($this->table,'*',$this->sortParent.' != 0 ORDER BY '.$this->sortField,true); 6043 6044 foreach($data as $row) 6045 { 6046 $p = $row[$this->sortParent]; 6047 6048 if($p != $previous) 6049 { 6050 $c = $parent[$p]; 6051 } 6052 6053 $c++; 6054 $previous = $p; 6055 6056 // echo "<br />".$row['forum_name']." with parent: ".$p." old: ".$row['forum_order']." new: ".$c; 6057 $sql->update($this->table, $this->sortField . ' = '.$c.' WHERE '.$this->pid.' = '.intval($row[$this->pid]).' LIMIT 1'); 6058 6059 } 6060 6061 6062 6063 6064 6065 } 6066*/ 6067 $this->afterSort($result, $_POST); 6068 6069 // e107::getLog()->addDebug(print_r($_POST,true))->toFile('SortAjax','Admin-UI Ajax Sort Log', true); 6070 // e107::getLog()->addDebug(print_r($updated,true))->toFile('SortAjax','Admin-UI Ajax Sort Log', true); 6071 // e107::getLog()->addDebug($qry)->toFile('SortAjax','Admin-UI Ajax Sort Log', true); 6072 6073 // eg. $qry = "UPDATE e107_faqs e, (SELECT @n := 249) m SET e.faq_order = @n := @n + 1 WHERE 1"; 6074 6075 } 6076 6077 /** 6078 * Generic List action page 6079 * @return string 6080 */ 6081 public function ListPage() 6082 { 6083 if($this->deleteConfirmScreen && !$this->getPosted('etrigger_delete_confirm') && $this->getPosted('delete_confirm_value')) 6084 { 6085 // 'edelete_confirm_data' set by single/batch delete trigger 6086 return $this->getUI()->getConfirmDelete($this->getPosted('delete_confirm_value')); // User confirmation expected 6087 } 6088 return $this->getUI()->getList(); 6089 } 6090 6091 /** 6092 * Generic List action page 6093 * @return string 6094 */ 6095 public function GridPage() 6096 { 6097 if($this->deleteConfirmScreen && !$this->getPosted('etrigger_delete_confirm') && $this->getPosted('delete_confirm_value')) 6098 { 6099 // 'edelete_confirm_data' set by single/batch delete trigger 6100 return $this->getUI()->getConfirmDelete($this->getPosted('delete_confirm_value')); // User confirmation expected 6101 } 6102 6103 return $this->getUI()->getList(null,'grid'); 6104 } 6105 6106 /** 6107 * List action observer 6108 * @return void 6109 */ 6110 public function ListAjaxObserver() 6111 { 6112 if($ufieldpref = $this->getUserPref()) 6113 { 6114 $this->fieldpref = $ufieldpref; 6115 } 6116 6117 $this->getTreeModel()->setParam('db_query', $this->_modifyListQry(false, false, 0, false, $this->listQry))->loadBatch(); 6118 } 6119 6120 6121 /** 6122 * List action observer 6123 * @return void 6124 */ 6125 public function GridAjaxObserver() 6126 { 6127 $this->ListAjaxObserver(); 6128 } 6129 6130 /** 6131 * Generic List action page (Ajax) 6132 * @return string 6133 */ 6134 public function ListAjaxPage() 6135 { 6136 return $this->getUI()->getList(true); 6137 } 6138 6139 6140 public function GridAjaxPage() 6141 { 6142 return $this->getUI()->getList(true,'grid'); 6143 } 6144 6145 /** 6146 * Generic Edit observer 6147 */ 6148 public function EditObserver() 6149 { 6150 $this->getModel()->load($this->getId()); 6151 $this->addTitle(); 6152 $this->addTitle('#'.$this->getId()); // Inform user of which record is being edited. 6153 } 6154 6155 /** 6156 * Generic Create submit trigger 6157 */ 6158 public function EditCancelTrigger() 6159 { 6160 $this->redirectAction('list', 'id'); 6161 } 6162 6163 /** 6164 * Generic Edit submit trigger 6165 */ 6166 public function EditSubmitTrigger() 6167 { 6168 $this->_manageSubmit('beforeUpdate', 'afterUpdate', 'onUpdateError', 'edit'); 6169 } 6170 6171 /** 6172 * Edit - send JS to page Header 6173 * @return none 6174 */ 6175 function EditHeader() 6176 { 6177 // e107::getJs()->requireCoreLib('core/admin.js'); 6178 e107::js('core','core/admin.js','prototype'); 6179 } 6180 6181 /** 6182 * Generic Edit page 6183 * @return string 6184 */ 6185 public function EditPage() 6186 { 6187 return $this->CreatePage(); 6188 } 6189 6190 /** 6191 * Generic Create observer 6192 * @return string 6193 */ 6194 public function CreateObserver() 6195 { 6196 $this->setTriggersEnabled(true); 6197 $this->addTitle(); 6198 } 6199 6200 /** 6201 * Generic Create submit trigger 6202 */ 6203 public function CreateCancelTrigger() 6204 { 6205 $this->redirectAction('list', 'id'); 6206 } 6207 6208 /** 6209 * Generic Create submit trigger 6210 */ 6211 public function CreateSubmitTrigger() 6212 { 6213 $this->_manageSubmit('beforeCreate', 'afterCreate', 'onCreateError'); 6214 } 6215 6216 /** 6217 * User defined pre-create logic, return false to prevent DB query execution 6218 * @param $new_data 6219 * @param $old_data 6220 */ 6221 public function beforeCreate($new_data, $old_data) 6222 { 6223 } 6224 6225 /** 6226 * User defined after-create logic 6227 * @param $new_data 6228 * @param $old_data 6229 * @param $id 6230 */ 6231 public function afterCreate($new_data, $old_data, $id) 6232 { 6233 } 6234 6235 /** 6236 * User defined error handling, return true to suppress model messages 6237 * @param $new_data 6238 * @param $old_data 6239 */ 6240 public function onCreateError($new_data, $old_data) 6241 { 6242 } 6243 6244 /** 6245 * User defined pre-update logic, return false to prevent DB query execution 6246 * @param $new_data 6247 * @param $old_data 6248 */ 6249 public function beforeUpdate($new_data, $old_data, $id) 6250 { 6251 } 6252 6253 6254 6255 /** 6256 * User defined after-update logic 6257 * @param $new_data 6258 * @param $old_data 6259 */ 6260 public function afterUpdate($new_data, $old_data, $id) 6261 { 6262 } 6263 6264 /** 6265 * User defined before pref saving logic 6266 * @param $new_data 6267 * @param $old_data 6268 */ 6269 public function beforePrefsSave($new_data, $old_data) 6270 { 6271 } 6272 6273 /** 6274 * User defined before pref saving logic 6275 */ 6276 public function afterPrefsSave() 6277 { 6278 6279 } 6280 6281 /** 6282 * User defined error handling, return true to suppress model messages 6283 */ 6284 public function onUpdateError($new_data, $old_data, $id) 6285 { 6286 } 6287 6288 /** 6289 * User defined after-update logic 6290 * @param mixed $result 6291 * @param array $selected 6292 * @return void 6293 */ 6294 public function afterCopy($result, $selected) 6295 { 6296 } 6297 6298 6299 /** 6300 * User defined after-sort logic 6301 * @param mixed $result 6302 * @param array $selected 6303 * @return void 6304 */ 6305 public function afterSort($result, $selected) 6306 { 6307 } 6308 6309 6310 /** 6311 * @return string 6312 */ 6313 public function renderHelp() 6314 { 6315 6316 } 6317 6318 /** 6319 * Create - send JS to page Header 6320 * @return none 6321 */ 6322 function CreateHeader() 6323 { 6324 // TODO - invoke it on className (not all textarea elements) 6325 //e107::getJs()->requireCoreLib('core/admin.js'); 6326 e107::js('core','core/admin.js','prototype'); 6327 } 6328 6329 /** 6330 * 6331 * @return string 6332 */ 6333 public function CreatePage() 6334 { 6335 return $this->getUI()->getCreate(); 6336 } 6337 6338 public function PrefsSaveTrigger() 6339 { 6340 $data = $this->getPosted(); 6341 6342 $beforePref = $data; 6343 unset($beforePref['e-token'],$beforePref['etrigger_save']); 6344 6345 $tmp = $this->beforePrefsSave($beforePref, $this->getConfig()->getPref()); 6346 6347 if(!empty($tmp)) 6348 { 6349 $data = $tmp; 6350 } 6351 6352 foreach($this->prefs as $k=>$v) // fix for empty checkboxes - need to save a value. 6353 { 6354 if(!isset($data[$k]) && $v['data'] !== false && ($v['type'] === 'checkboxes' || $v['type'] === 'checkbox')) 6355 { 6356 $data[$k] = null; 6357 } 6358 } 6359 6360 foreach($data as $key=>$val) 6361 { 6362 6363 if(!empty($this->prefs[$key]['multilan'])) 6364 { 6365 6366 if(is_string($this->getConfig()->get($key))) // most likely upgraded to multilan=>true, so reset to an array structure. 6367 { 6368 $this->getConfig()->setPostedData($key, array(e_LANGUAGE => $val), false); 6369 } 6370 else 6371 { 6372 $lang = key($val); 6373 $value = $val[$lang]; 6374 $this->getConfig()->setData($key.'/'.$lang, str_replace("'", ''', $value)); 6375 } 6376 6377 } 6378 else 6379 { 6380 $this->getConfig()->setPostedData($key, $val, false); 6381 } 6382 6383 } 6384 6385 $this->getConfig()->save(true); 6386 6387 $this->afterPrefsSave(); 6388 6389/* 6390 $this->getConfig() 6391 ->setPostedData($this->getPosted(), null, false) 6392 //->setPosted('not_existing_pref_test', 1) 6393 ->save(true); 6394*/ 6395 6396 $this->getConfig()->setMessages(); 6397 6398 } 6399 6400 public function PrefsObserver() 6401 { 6402 $this->addTitle(); 6403 } 6404 6405 public function PrefsPage() 6406 { 6407 return $this->getUI()->getSettings(); 6408 } 6409 6410 /** 6411 * Parent overload 6412 * @return e_admin_ui 6413 */ 6414 protected function parseAliases() 6415 { 6416 // parse table 6417 if(strpos($this->table, '.') !== false) 6418 { 6419 $tmp = explode('.', $this->table, 2); 6420 $this->table = $tmp[1]; 6421 $this->tableAlias = $tmp[0]; 6422 unset($tmp); 6423 } 6424 6425 parent::parseAliases(); 6426 6427 return $this; 6428 } 6429 6430 public function getPrimaryName() 6431 { 6432 // Option for working with tables having no PID 6433 if(!varset($this->pid) && vartrue($this->fields) && false !== $this->pid) 6434 { 6435 $message = e107::getParser()->toHTML(LAN_UI_NOPID_ERROR,true); 6436 e107::getMessage()->add($message, E_MESSAGE_WARNING); 6437 } 6438 6439 return $this->pid; 6440 } 6441 6442 public function getTableName($alias = false, $prefix = false) 6443 { 6444 if($alias) return ($this->tableAlias ? $this->tableAlias : ''); 6445 return ($prefix ? '#' : '').$this->table; 6446 } 6447 6448 /** 6449 * Validation rules retrieved from controller object 6450 * @return array 6451 */ 6452 public function getValidationRules() 6453 { 6454 return $this->validationRules; 6455 } 6456 6457 /** 6458 * Data Field array retrieved from controller object 6459 * @return array 6460 */ 6461 public function getDataFields() 6462 { 6463 return $this->dataFields; 6464 } 6465 6466 6467 /** 6468 * Set read and write parms with drop-down-list array data (ie. type='dropdown') 6469 * @param str $field 6470 * @param array $array [optional] 6471 * @return none 6472 */ 6473 public function setDropDown($field,$array) //TODO Have Miro check this. 6474 { 6475 $this->fields[$field]['readParms'] = $array; 6476 $this->fields[$field]['writeParms'] = $array; 6477 } 6478 6479 6480 /** 6481 * Set Config object 6482 * @return e_admin_ui 6483 */ 6484 protected function _setConfig() 6485 { 6486 $this->_pref = $this->pluginName === 'core' ? e107::getConfig() : e107::getPlugConfig($this->pluginName); 6487 6488 if($this->pluginName !== 'core' && !e107::isInstalled($this->pluginName)) 6489 { 6490 $obj = get_class($this); 6491 e107::getMessage()->addWarning($obj."The plugin is not installed or \$pluginName: is not valid. (".$this->pluginName. ")"); // debug only. 6492 return $this; 6493 } 6494 6495 $dataFields = $validateRules = array(); 6496 foreach ($this->prefs as $key => $att) 6497 { 6498 // create dataFields array 6499 $dataFields[$key] = vartrue($att['data'], 'string'); 6500 6501 // create validation array 6502 if(vartrue($att['validate'])) 6503 { 6504 $validateRules[$key] = array((true === $att['validate'] ? 'required' : $att['validate']), varset($att['rule']), $att['title'], varset($att['error'], $att['help'])); 6505 } 6506 /* Not implemented in e_model yet 6507 elseif(vartrue($att['check'])) 6508 { 6509 $validateRules[$key] = array($att['check'], varset($att['rule']), $att['title'], varset($att['error'], $att['help'])); 6510 }*/ 6511 } 6512 $this->_pref->setDataFields($dataFields)->setValidationRules($validateRules); 6513 6514 return $this; 6515 } 6516 6517 /** 6518 * Set current model 6519 * 6520 * @return e_admin_ui 6521 */ 6522 public function _setModel() 6523 { 6524 // try to create dataFields array if missing 6525 6526 if(!$this->dataFields) 6527 { 6528 $this->dataFields = array(); 6529 foreach ($this->fields as $key => $att) 6530 { 6531 if($key == $this->pid && empty($att['data'])) // Set integer as default for primary ID when not specified. MySQL Strict Fix. 6532 { 6533 $this->dataFields[$key] = 'int'; 6534 continue; 6535 } 6536 6537 if(varset($att['type']) === 'comma' && (empty($att['data']) || empty($att['rule']))) 6538 { 6539 $att['data'] = 'set'; 6540 $att['validate'] = 'set'; 6541 $_parms = vartrue($att['writeParms'], array()); 6542 if(is_string($_parms)) parse_str($_parms, $_parms); 6543 unset($_parms['__options']); 6544 $att['rule'] = $_parms; 6545 unset($_parms); 6546 } 6547 6548 if(!empty($att['data']) && $att['data'] === 'array' && ($this->getAction() === 'inline')) // FIX for arrays being saved incorrectly with inline editing. 6549 { 6550 $att['data'] = 'set'; 6551 } 6552 6553 if(($key !== 'options' && false !== varset($att['data']) && null !== varset($att['type'],null) && !vartrue($att['noedit'])) || vartrue($att['forceSave'])) 6554 { 6555 $this->dataFields[$key] = vartrue($att['data'], 'str'); 6556 if(!empty($att['type'])) 6557 { 6558 $this->fieldInputTypes[$key] = $att['type']; 6559 } 6560 } 6561 6562 6563 6564 } 6565 } 6566 6567 // TODO - do it in one loop, or better - separate method(s) -> convertFields(validate), convertFields(data),... 6568 if(!$this->validationRules) 6569 { 6570 $this->validationRules = array(); 6571 foreach ($this->fields as $key => $att) 6572 { 6573 if(null === varset($att['type'], null) || vartrue($att['noedit'])) 6574 { 6575 continue; 6576 } 6577 if(vartrue($att['validate'])) 6578 { 6579 $this->validationRules[$key] = array((true === $att['validate'] ? 'required' : $att['validate']), varset($att['rule']), $att['title'], varset($att['error'], vartrue($att['help']))); 6580 } 6581 /*elseif(vartrue($att['check'])) could go? 6582 { 6583 $this->checkRules[$key] = array($att['check'], varset($att['rule']), $att['title'], varset($att['error'], $att['help'])); 6584 }*/ 6585 } 6586 } 6587 6588 // don't touch it if already exists 6589 if($this->_model) return $this; 6590 6591 // default model 6592 6593 6594 $this->_model = new e_admin_model(); 6595 $this->_model->setModelTable($this->table) 6596 ->setFieldIdName($this->pid) 6597 ->setUrl($this->url) 6598 ->setValidationRules($this->validationRules) 6599 ->setDbTypes($this->fieldTypes) 6600 ->setFieldInputTypes($this->fieldInputTypes) 6601 ->setDataFields($this->dataFields) 6602 ->setMessageStackName('admin_ui_model_'.$this->table) 6603 ->setParam('db_query', $this->editQry); 6604 6605 return $this; 6606 } 6607 6608 /** 6609 * Set current tree 6610 * @return e_admin_ui 6611 */ 6612 public function _setTreeModel() 6613 { 6614 // default tree model 6615 $this->_tree_model = new e_admin_tree_model(); 6616 $this->_tree_model->setModelTable($this->table) 6617 ->setFieldIdName($this->pid) 6618 ->setUrl($this->url) 6619 ->setMessageStackName('admin_ui_tree_'.$this->table) 6620 ->setParams(array('model_class' => 'e_admin_model', 6621 'model_message_stack' => 'admin_ui_model_'.$this->table, 6622 'db_query' => $this->listQry, 6623 // Information necessary for PHP-based tree sort 6624 'sort_parent' => $this->getSortParent(), 6625 'sort_field' => $this->getSortField(), 6626 'primary_field' => $this->getPrimaryName(), 6627 )); 6628 6629 return $this; 6630 } 6631 6632 /** 6633 * Get extended (UI) Form instance 6634 * 6635 * @return e_admin_ui 6636 */ 6637 public function _setUI() 6638 { 6639 if($this->getParam('ui')) 6640 { 6641 $this->_ui = $this->getParam('ui'); 6642 $this->setParam('ui', null); 6643 } 6644 else// default ui 6645 { 6646 $this->_ui = new e_admin_form_ui($this); 6647 } 6648 return $this; 6649 } 6650 6651} 6652 6653class e_admin_form_ui extends e_form 6654{ 6655 /** 6656 * @var e_admin_ui 6657 */ 6658 protected $_controller = null; 6659 protected $_list_view = null; 6660 6661 6662 6663 /** 6664 * Constructor 6665 * @param e_admin_controller_ui $controller 6666 * @param boolean $tabindex [optional] enable form element auto tab-indexing 6667 */ 6668 function __construct($controller, $tabindex = false) 6669 { 6670 $this->_controller = $controller; 6671 parent::__construct($tabindex); 6672 6673 // protect current methods from conflict. 6674 $this->preventConflict(); 6675 // user constructor 6676 $this->init(); 6677 } 6678 6679 protected function preventConflict() 6680 { 6681 $err = false; 6682 $fields = $this->getController()->getFields(); 6683 6684 if(empty($fields)) 6685 { 6686 return null; 6687 } 6688 6689 foreach($fields as $field => $foptions) 6690 { 6691 // check form custom methods 6692 if(vartrue($foptions['type']) === 'method' && method_exists('e_form', $field)) // check even if type is not method. - just in case of an upgrade later by 3rd-party. 6693 { 6694 $message = e107::getParser()->lanVars(LAN_UI_FORM_METHOD_ERROR, array('x'=>$field), true); 6695 e107::getMessage()->addError($message); 6696 $err = true; 6697 } 6698 } 6699 6700 /*if($err) 6701 { 6702 //echo $err; 6703 //exit; 6704 }*/ 6705 } 6706 6707 6708 6709 /** 6710 * User defined init 6711 */ 6712 public function init() 6713 { 6714 } 6715 6716 6717 /** 6718 * @todo Get a 'depth/level' field working with mysql and change the 'level' accordingly 6719 * @param mixed $curVal 6720 * @param string $mode read|write|inline 6721 * @param array $parm 6722 * @return array|string 6723 */ 6724 public function treePrefix($curVal, $mode, $parm) 6725 { 6726 $controller = $this->getController(); 6727 $parentField = $controller->getSortParent(); 6728 $treePrefixField = $controller->getTreePrefix(); 6729 $parent = $controller->getListModel()->get($parentField); 6730 $level = $controller->getListModel()->get("_depth"); 6731 6732 6733 if($mode === 'read') 6734 { 6735 6736 $inline = $this->getController()->getFieldAttr($treePrefixField,'inline'); 6737 6738 if($inline === true) 6739 { 6740 return $curVal; 6741 } 6742 6743 $level_image = $parent ? str_replace('level-x','level-'.$level, ADMIN_CHILD_ICON) : ''; 6744 6745 return ($parent) ? $level_image.$curVal : $curVal; 6746 6747 } 6748 6749 6750 if($mode === 'inline') 6751 { 6752 $ret = array('inlineType'=>'text'); 6753 6754 if(!empty($parent)) 6755 { 6756 $ret['inlineParms'] = array('pre'=> str_replace('level-x','level-'.$level, ADMIN_CHILD_ICON)); 6757 } 6758 6759 6760 return $ret; 6761 } 6762 6763 6764/* 6765 if($mode == 'write') // not used. 6766 { 6767 // return $frm->text('forum_name',$curVal,255,'size=xxlarge'); 6768 } 6769 6770 if($mode == 'filter') 6771 { 6772 return; 6773 } 6774 if($mode == 'batch') 6775 { 6776 return; 6777 } 6778*/ 6779 6780 6781 6782 6783 } 6784 6785 6786 /** 6787 * Generic DB Record Creation Form. 6788 * @return string 6789 */ 6790 function getCreate() 6791 { 6792 $controller = $this->getController(); 6793 $request = $controller->getRequest(); 6794 if($controller->getId()) 6795 { 6796 $legend = e107::getParser()->lanVars(LAN_UI_EDIT_LABEL, $controller->getId()); // sprintXXX(LAN_UI_EDIT_LABEL, $controller->getId()); 6797 $form_start = vartrue($controller->headerUpdateMarkup); 6798 $form_end = vartrue($controller->footerUpdateMarkup); 6799 } 6800 else 6801 { 6802 $legend = LAN_UI_CREATE_LABEL; 6803 $form_start = vartrue($controller->headerCreateMarkup); 6804 $form_end = vartrue($controller->footerCreateMarkup); 6805 } 6806 6807 $tabs = $controller->getTabs(); 6808 6809 if($multiLangInfo = $this->renderLanguageTableInfo()) 6810 { 6811 if(empty($tabs)) 6812 { 6813 $head = "<div id='admin-ui-edit-db-language' class='text-right'>".$multiLangInfo."</div>"; 6814 } 6815 else 6816 { 6817 $head = "<div id='admin-ui-edit-db-language' class='text-right tabs'>".$multiLangInfo."</div>"; 6818 } 6819 } 6820 else 6821 { 6822 $head = ''; 6823 } 6824 6825 $forms = $models = array(); 6826 $forms[] = array( 6827 'id' => $this->getElementId(), 6828 'header' => $head, 6829 'footer' => '', 6830 //'url' => e_SELF, 6831 //'query' => 'self', or custom GET query, self is default 6832 'fieldsets' => array( 6833 'create' => array( 6834 'tabs' => $tabs, //used within a single form. 6835 'legend' => $legend, 6836 'fields' => $controller->getFields(), //see e_admin_ui::$fields 6837 'header' => $form_start, //XXX Unused? 6838 'footer' => $form_end, //XXX Unused? 6839 'after_submit_options' => $controller->getAfterSubmitOptions(), // or true for default redirect options 6840 'after_submit_default' => $request->getPosted('__after_submit_action', $controller->getDefaultAction()), // or true for default redirect options 6841 'triggers' => $controller->getDefaultTrigger(), // standard create/update-cancel triggers 6842 ) 6843 ) 6844 ); 6845 6846 $models[] = $controller->getModel(); 6847 6848 return $this->renderCreateForm($forms, $models, e_AJAX_REQUEST); 6849 } 6850 6851 /** 6852 * Generic Settings Form. 6853 * @return string 6854 */ 6855 function getSettings() 6856 { 6857 $controller = $this->getController(); 6858 // $request = $controller->getRequest(); 6859 $legend = LAN_UI_PREF_LABEL; 6860 $forms = $models = array(); 6861 $forms[] = array( 6862 'id' => $this->getElementId(), 6863 //'url' => e_SELF, 6864 //'query' => 'self', or custom GET query, self is default 6865 'tabs' => false, // TODO - NOT IMPLEMENTED YET - enable tabs (only if fieldset count is > 1) 6866 'fieldsets' => array( 6867 'settings' => array( 6868 'tabs' => $controller->getPrefTabs(), //used within a single form. 6869 'legend' => $legend, 6870 'fields' => $controller->getPrefs(), //see e_admin_ui::$prefs 6871 'after_submit_options' => false, 6872 'after_submit_default' => false, // or true for default redirect options 6873 'triggers' => array('save' => array(LAN_SAVE, 'update')), // standard create/update-cancel triggers 6874 ) 6875 ) 6876 ); 6877 $models[] = $controller->getConfig(); 6878 6879 // print_a($forms); 6880 6881 return $this->renderCreateForm($forms, $models, e_AJAX_REQUEST); 6882 } 6883 6884 6885 /** 6886 * Integrate e_addon data into the list model. 6887 * @param e_tree_model $tree 6888 * @param array $fields 6889 * @param string $pid 6890 * @return null 6891 */ 6892 private function setAdminAddonModel(e_tree_model $tree, $fields, $pid) 6893 { 6894 6895 $event= $this->getController()->getEventName(); 6896 6897 $arr = array(); 6898 6899 /** @var e_tree_model $model */ 6900 foreach($tree->getTree() as $model) 6901 { 6902 foreach($fields as $fld) 6903 { 6904 6905 if(strpos($fld,'x_') !== 0) 6906 { 6907 continue; 6908 } 6909 6910 list($prefix,$plug,$field) = explode("_",$fld,3); 6911 6912 if($prefix !== 'x' || empty($field) || empty($plug)) 6913 { 6914 continue; 6915 } 6916 6917 $id = $model->get($pid); 6918 6919 if(!empty($id)) 6920 { 6921 $arr[$plug][$field][$id] = $model; 6922 } 6923 } 6924 6925 6926 } 6927 6928 6929 foreach($arr as $plug=>$field) 6930 { 6931 6932 if($obj = e107::getAddon($plug, 'e_admin')) 6933 { 6934 foreach($field as $fld=>$var) 6935 { 6936 $ids = implode(",", array_keys($var)); 6937 6938 $value = (array) e107::callMethod($obj,'load', $event,$ids); 6939 6940 // $value = (array) $obj->load($event, $ids); 6941 6942 foreach($var as $id=>$model) 6943 { 6944 $model->set("x_".$plug."_".$fld, varset($value[$id][$fld],null)); 6945 } 6946 } 6947 } 6948 6949 } 6950 6951 } 6952 6953 6954 /** 6955 * Create list view 6956 * Search for the following GET variables: 6957 * - from: integer, current page 6958 * 6959 * @return string 6960 */ 6961 public function getList($ajax = false, $view='default') 6962 { 6963 $tp = e107::getParser(); 6964 $this->_list_view = $view; 6965 $controller = $this->getController(); 6966 6967 $request = $controller->getRequest(); 6968 $id = $this->getElementId(); 6969 $pid = $controller->getPrimaryName(); 6970 $tree = $options = array(); 6971 $tree[$id] = $controller->getTreeModel(); 6972 6973 6974 6975 6976 if(deftrue('e_DEBUG_TREESORT') && $view === 'default') 6977 { 6978 $controller->getTreeModelSorted(); 6979 } 6980 6981 // if going through confirm screen - no JS confirm 6982 $controller->setFieldAttr('options', 'noConfirm', $controller->deleteConfirmScreen); 6983 6984 $fields = $controller->getFields(); 6985 6986 $this->setAdminAddonModel($tree[$id], array_keys($fields), $pid); 6987 6988 // checks dispatcher acess/perms for create/edit/delete access in list mode. 6989 $mode = $controller->getMode(); 6990 $deleteRoute = $mode."/delete"; 6991 $editRoute = $mode."/edit"; 6992 $createRoute = $mode."/create"; 6993 6994 if(!$controller->getDispatcher()->hasRouteAccess($createRoute)) // disable the batchCopy option. 6995 { 6996 $controller->setBatchCopy(false); 6997 } 6998 6999 if(!$controller->getDispatcher()->hasRouteAccess($deleteRoute)) // disable the delete button and batch delete. 7000 { 7001 $fields['options']['readParms']['deleteClass'] = e_UC_NOBODY; 7002 $controller->setBatchDelete(false); 7003 } 7004 7005 if(!$controller->getDispatcher()->hasRouteAccess($editRoute)) 7006 { 7007 $fields['options']['readParms']['editClass'] = e_UC_NOBODY; // display the edit button. 7008 foreach($options[$id]['fields'] as $k=>$v) // disable inline editing. 7009 { 7010 $fields[$k]['inline'] = false; 7011 } 7012 } 7013 7014 if(!$controller->getSortField()) 7015 { 7016 $fields['options']['sort'] = false; 7017 } 7018 7019 if($treefld = $controller->getTreePrefix()) 7020 { 7021 $fields[$treefld]['type'] = 'method'; 7022 $fields[$treefld]['method'] = 'treePrefix'; /* @see e_admin_form_ui::treePrefix(); */ 7023 7024 $tr = $controller->getTreeModel()->toArray(); 7025 7026 foreach($tr as $row) 7027 { 7028 e107::getDebug()->log($row[$treefld].' > '.$row['_treesort']); 7029 } 7030 7031 } 7032 7033 7034 7035 // ------------------------------------------ 7036 7037 $coreBatchOptions = array( 7038 'delete' => $controller->getBatchDelete(), 7039 'copy' => $controller->getBatchCopy(), 7040 'url' => $controller->getBatchLink(), 7041 'featurebox' => $controller->getBatchFeaturebox(), 7042 'export' => $controller->getBatchExport(), 7043 7044 ); 7045 7046 7047 $options[$id] = array( 7048 'id' => $this->getElementId(), // unique string used for building element ids, REQUIRED 7049 'pid' => $pid, // primary field name, REQUIRED 7050 'query' => $controller->getFormQuery(), // work around - see form in newspost.php (submitted news) 7051 'head_query' => $request->buildQueryString('field=[FIELD]&asc=[ASC]&from=[FROM]', false), // without field, asc and from vars, REQUIRED 7052 'np_query' => $request->buildQueryString(array(), false, 'from'), // without from var, REQUIRED for next/prev functionality 7053 'legend' => $controller->getPluginTitle(), // hidden by default 7054 'form_pre' => !$ajax ? $this->renderFilter($tp->post_toForm(array($controller->getQuery('searchquery'), $controller->getQuery('filter_options'))), $controller->getMode().'/'.$controller->getAction()) : '', // needs to be visible when a search returns nothing 7055 'form_post' => '', // markup to be added after closing form element 7056 'fields' => $fields, // see e_admin_ui::$fields 7057 'fieldpref' => $controller->getFieldPref(), // see e_admin_ui::$fieldpref 7058 'table_pre' => '', // markup to be added before opening table element 7059 // 'table_post' => !$tree[$id]->isEmpty() ? $this->renderBatch($controller->getBatchDelete(),$controller->getBatchCopy(),$controller->getBatchLink(),$controller->getBatchFeaturebox()) : '', 7060 7061 'table_post' => $this->renderBatch($coreBatchOptions, $controller->getBatchOptions()), 7062 'fieldset_pre' => '', // markup to be added before opening fieldset element 7063 'fieldset_post' => '', // markup to be added after closing fieldset element 7064 'grid' => $controller->getGrid(), 7065 'perPage' => $controller->getPerPage(), // if 0 - no next/prev navigation 7066 'from' => $controller->getQuery('from', 0), // current page, default 0 7067 'field' => $controller->getQuery('field'), //current order field name, default - primary field 7068 'asc' => $controller->getQuery('asc', 'desc'), //current 'order by' rule, default 'asc' 7069 ); 7070 7071 7072 7073 if($view === 'grid') 7074 { 7075 return $this->renderGridForm($options, $tree, $ajax); 7076 } 7077 7078 return $this->renderListForm($options, $tree, $ajax); 7079 } 7080 7081 7082 7083 7084 7085 7086 public function getConfirmDelete($ids, $ajax = false) 7087 { 7088 $controller = $this->getController(); 7089 $request = $controller->getRequest(); 7090 $fieldsets = array(); 7091 $forms = array(); 7092 $id_array = explode(',', $ids); 7093 $delcount = count($id_array); 7094 7095 if(!empty($controller->deleteConfirmMessage)) 7096 { 7097 e107::getMessage()->addWarning(str_replace("[x]","<b>".$delcount."</b>", $controller->deleteConfirmMessage)); 7098 } 7099 else 7100 { 7101 e107::getMessage()->addWarning(str_replace("[x]","<b>".$delcount."</b>",LAN_UI_DELETE_WARNING)); 7102 } 7103 7104 $fieldsets['confirm'] = array( 7105 'fieldset_pre' => '', // markup to be added before opening fieldset element 7106 'fieldset_post' => '', // markup to be added after closing fieldset element 7107 'table_head' => '', // markup between <thead> tag 7108 // Colgroup Example: array(0 => array('class' => 'label', 'style' => 'text-align: left'), 1 => array('class' => 'control', 'style' => 'text-align: left')); 7109 'table_colgroup' => '', // array to be used for creating markup between <colgroup> tag (<col> list) 7110 'table_pre' => '', // markup to be added before opening table element 7111 'table_post' => '', // markup to be added after closing table element 7112 'table_rows' => '', // rows array (<td> tags) 7113 'table_body' => '', // string body - used only if rows empty 7114 'pre_triggers' => '', 7115 'triggers' => array('hidden' => $this->hidden('etrigger_delete['.$ids.']', $ids) . $this->token(), 'delete_confirm' => array(LAN_CONFDELETE, 'confirm', $ids), 'cancel' => array(LAN_CANCEL, 'cancel')), 7116 ); 7117 if($delcount > 1) 7118 { 7119 $fieldsets['confirm']['triggers']['hidden'] = $this->hidden('etrigger_batch', 'delete'); 7120 } 7121 7122 $id = null; 7123 $forms[$id] = array( 7124 'id' => $this->getElementId(), // unique string used for building element ids, REQUIRED 7125 'url' => e_REQUEST_SELF, // default 7126 'query' => $request->buildQueryString(array(), true, 'ajax_used'), // - ajax_used is now removed from QUERY_STRING - class2 7127 'legend' => $controller->addTitle(LAN_UI_DELETE_LABEL), // hidden by default 7128 'form_pre' => '', // markup to be added before opening form element 7129 'form_post' => '', // markup to be added after closing form element 7130 'header' => '', // markup to be added after opening form element 7131 'footer' => '', // markup to be added before closing form element 7132 'fieldsets' => $fieldsets, 7133 ); 7134 return $this->renderForm($forms, $ajax); 7135 } 7136 7137 7138 /** 7139 * Render pagination 7140 * @return string 7141 */ 7142 public function renderPagination() 7143 { 7144 if($this->_list_view === 'grid' && $this->getController()->getGrid('carousel') === true) 7145 { 7146 return '<div class="btn-group" > 7147 <a id="admin-ui-carousel-prev" class="btn btn-default btn-secondary" href="#admin-ui-carousel" data-slide="prev"><i class="fa fa-backward"></i></a> 7148 <a id="admin-ui-carousel-index" class="btn btn-default btn-secondary">1</a> 7149 <a id="admin-ui-carousel-next" class="btn btn-default btn-secondary" href="#admin-ui-carousel" data-slide="next"><i class="fa fa-forward"></i></a> 7150 </div>'; 7151 } 7152 7153 $tree = $this->getController()->getTreeModel(); 7154 $totalRecords = $tree->getTotal(); 7155 $perPage = $this->getController()->getPerPage(); 7156 $fromPage = $this->getController()->getQuery('from', 0); 7157 7158 $vars = $this->getController()->getQuery(); 7159 $vars['from'] = '[FROM]'; 7160 7161 $paginate = http_build_query($vars, null, '&'); 7162 7163 return $this->pagination(e_REQUEST_SELF.'?'.$paginate,$totalRecords,$fromPage,$perPage,array('template'=>'basic')); 7164 7165 } 7166 7167 function renderFilter($current_query = array(), $location = '', $input_options = array()) 7168 { 7169 if(!$input_options) $input_options = array('size' => 20); 7170 if(!$location) 7171 { 7172 $location = 'main/list'; //default location 7173 } 7174 $l = e107::getParser()->post_toForm(explode('/', $location)); 7175 if(!is_array($input_options)) 7176 { 7177 parse_str($input_options, $input_options); 7178 } 7179 $input_options['id'] = false; 7180 $input_options['class'] = 'tbox input-text filter input-xlarge '; 7181 $controller = $this->getController(); 7182 $filter_pre = vartrue($controller->preFilterMarkup); 7183 $filter_post = vartrue($controller->postFilterMarkup); 7184 $filter_preserve_var = array(); 7185 // method requires controller - stanalone advanced usage not possible 7186 if($this->getController()) 7187 { 7188 $get = $this->getController()->getQuery(); 7189 foreach ($get as $key => $value) 7190 { 7191 if($key === 'searchquery' || $key === 'filter_options' || $key === 'etrigger_filter') 7192 { 7193 continue; 7194 } 7195 7196 // Reset pager after filtering. 7197 if ($key === 'from') 7198 { 7199 continue; 7200 } 7201 7202 $key = preg_replace('/[^\w]/', '', $key); 7203 $filter_preserve_var[] = $this->hidden($key, rawurlencode($value)); 7204 } 7205 } 7206 else 7207 { 7208 $filter_preserve_var[] = $this->hidden('mode', $l[0]); 7209 $filter_preserve_var[] = $this->hidden('action', $l[1]); 7210 } 7211 7212 7213 // $tree = $this->getTree(); 7214 // $total = $this->getTotal(); 7215 $grid = $this->getController()->getGrid(); 7216 7217 7218 $gridToggle = ''; 7219 7220 if(!empty($grid) && varset($grid['toggleButton']) !==false) 7221 { 7222 $gridAction = $this->getController()->getAction() === 'grid' ? 'list' : 'grid'; 7223 $gridQuery = (array) $_GET; 7224 $gridQuery['action'] = $gridAction; 7225 $toggleUrl = e_REQUEST_SELF."?".http_build_query($gridQuery, null, '&'); 7226 $gridIcon = ($gridAction === 'grid') ? ADMIN_GRID_ICON : ADMIN_LIST_ICON; 7227 $gridTitle = ($gridAction === 'grid') ? LAN_UI_VIEW_GRID_LABEL : LAN_UI_VIEW_LIST_LABEL; 7228 $gridToggle = "<a class='btn btn-default' href='".$toggleUrl."' title=\"".$gridTitle."\">".$gridIcon."</a>"; 7229 } 7230 7231 // <!--<i class='fa fa-search searchquery form-control-feedback form-control-feedback-left'></i>--> 7232 7233 $text = " 7234 <form method='get' action='".e_REQUEST_SELF."'> 7235 <fieldset id='admin-ui-list-filter' class='e-filter'> 7236 <legend class='e-hideme'>".LAN_LABEL_LABEL_SELECTED."</legend> 7237 ".$filter_pre." 7238 <div class='row-fluid'> 7239 <div class='left form-inline span8 col-md-8' > 7240 <span id='admin-ui-list-search' class='form-group has-feedback has-feedback-left'> 7241 ".$this->text('searchquery', $current_query[0], 50, $input_options)." 7242 </span> 7243 ".$this->select_open('filter_options', array('class' => 'form-control e-tip tbox select filter', 'id' => false, 'title'=>LAN_FILTER))." 7244 ".$this->option(LAN_FILTER_LABEL_DISPLAYALL, '')." 7245 ".$this->option(LAN_FILTER_LABEL_CLEAR, '___reset___')." 7246 ".$this->renderBatchFilter('filter', $current_query[1])." 7247 ".$this->select_close()." 7248 <div class='e-autocomplete'></div> 7249 ".implode("\n", $filter_preserve_var)." 7250 ".$this->admin_button('etrigger_filter', 'etrigger_filter', 'filter e-hide-if-js', ADMIN_FILTER_ICON, array('id' => false,'title'=>LAN_FILTER))." 7251 7252 ".$this->renderPagination()." 7253 <span class='indicator' style='display: none;'> 7254 <img src='".e_IMAGE_ABS."generic/loading_16.gif' class='icon action S16' alt='".LAN_LOADING."' /> 7255 </span> 7256 ".$gridToggle." 7257 </div> 7258 <div id='admin-ui-list-db-language' class='span4 col-md-4 text-right' >"; 7259 7260 7261 // Let Admin know which language table is being saved to. (avoid default table overwrites) 7262 $text .= $this->renderLanguageTableInfo(); 7263 7264 $text .= " 7265 </div> 7266 </div> 7267 ".$filter_post." 7268 </fieldset> 7269 </form> 7270 "; 7271 7272 7273 e107::js('core','scriptaculous/controls.js','prototype', 2); 7274 //TODO - external JS 7275 e107::js('footer-inline'," 7276 7277 //autocomplete fields 7278 \$\$('input[name=searchquery]').each(function(el, cnt) { 7279 if(!cnt) el.activate(); 7280 else return; 7281 new Ajax.Autocompleter(el, el.next('div.e-autocomplete'), '".e_REQUEST_SELF."?mode=".$l[0]."&action=filter', { 7282 paramName: 'searchquery', 7283 minChars: 2, 7284 frequency: 0.5, 7285 afterUpdateElement: function(txt, li) { 7286 var cfrm = el.up('form'), cont = cfrm.next('.e-container'); 7287 if(!cont) { 7288 return; 7289 } 7290 cfrm.submitForm(cont); 7291 }, 7292 indicator: el.next('span.indicator'), 7293 parameters: 'ajax_used=1' 7294 }); 7295 var sel = el.next('select.filter'); 7296 if(sel) { 7297 sel.observe('change', function (e) { 7298 var cfrm = e.element().up('form'), cont = cfrm.next('.e-container'); 7299 if(cfrm && cont && e.element().value != '___reset___') { 7300 e.stop(); 7301 cfrm.submitForm(cont); 7302 return; 7303 } 7304 e107Helper.selectAutoSubmit(e.element()); 7305 }); 7306 } 7307 }); 7308 ",'prototype'); 7309 7310 // TODO implement ajax queue 7311 // FIXME 7312 // dirty way to register events after ajax update - DO IT RIGHT - see all.jquery, create object and use handler, 7313 // re-register them global after ajax update (context)... use behaviors and call e107.attachBehaviors(); 7314 e107::js('footer-inline'," 7315 var filterRunning = false, request; 7316 var applyAfterAjax = function(context) { 7317 \$('.e-expandit', context).click(function () { 7318 var href = (\$(this).is('a')) ? \$(this).attr('href') : ''; 7319 if(href == '' && \$(this).attr('data-target')) 7320 { 7321 href = '#' + \$(this).attr('data-target'); 7322 } 7323 if(href === '#' || href == '') 7324 { 7325 idt = \$(this).nextAll('div'); 7326 \$(idt).toggle('slow'); 7327 return true; 7328 } 7329 //var id = $(this).attr('href'); 7330 \$(href).toggle('slow'); 7331 return false; 7332 }); 7333 \$('input.toggle-all', context).click(function(evt) { 7334 var selector = 'input[type=\"checkbox\"].checkbox'; 7335 if(\$(this).val().startsWith('jstarget:')) { 7336 selector = 'input[type=\"checkbox\"][name^=\"' + \$(this).val().split(/jstarget\:/)[1] + '\"]'; 7337 } 7338 7339 if(\$(this).is(':checked')){ 7340 \$(selector).attr('checked', 'checked'); 7341 } 7342 else{ 7343 \$(selector).removeAttr('checked'); 7344 } 7345 }); 7346 }; 7347 var searchQueryHandler = function (e) { 7348 var el = \$(this), frm = el.parents('form'), cont = frm.nextAll('.e-container'); 7349 if(cont.length < 1 || frm.length < 1 || (el.val().length > 0 && el.val().length < 3)) return; 7350 e.preventDefault(); 7351 7352 if(filterRunning && request) request.abort(); 7353 filterRunning = true; 7354 7355 cont.css({ opacity: 0.5 }); 7356 7357 request = \$.get(frm.attr('action'), frm.serialize(), function(data){ 7358 filterRunning = false; 7359 setTimeout(function() { 7360 if(filterRunning) { 7361 //cont.css({ opacity: 1 }); 7362 return; 7363 } 7364 cont.html(data).css({ opacity: 1 }); 7365 // TODO remove applyAfterAjax() and use behaviors! 7366 applyAfterAjax(cont); 7367 // Attach behaviors to the newly loaded contents. 7368 e107.attachBehaviors(); 7369 }, 700); 7370 }, 'html') 7371 .error(function() { 7372 filterRunning = false; 7373 cont.css({ opacity: 1 }); 7374 }); 7375 }; 7376 \$('#searchquery').on('keyup', searchQueryHandler); 7377 ", 'jquery'); 7378 7379 return $text; 7380 } 7381 7382 7383 private function renderLanguageTableInfo() 7384 { 7385 7386 if(!e107::getConfig()->get('multilanguage')) 7387 { 7388 return null; 7389 } 7390 7391 $curTable = $this->getController()->getTableName(); 7392 $sitelanguage = e107::getConfig()->get('sitelanguage'); 7393 7394 $val = e107::getDb()->hasLanguage($curTable, true); 7395 7396 if($val === false) 7397 { 7398 return null; 7399 } 7400 7401 if($curTable != e107::getDb()->hasLanguage($curTable)) 7402 { 7403 $lang = e107::getDb()->mySQLlanguage; 7404 } 7405 else 7406 { 7407 $lang = $sitelanguage; 7408 } 7409 7410 $def = deftrue('LAN_UI_USING_DATABASE_TABLE','Using [x] database table'); 7411 $diz = e107::getParser()->lanVars($def, $lang); // "Using ".$lang." database table"; 7412 $class = ($sitelanguage == $lang) ? "default" : ""; 7413 7414 $text = "<span class='adminui-language-table-info ".$class." e-tip' title=\"".$diz."\">"; 7415 $text .= e107::getParser()->toGlyph('fa-hdd-o'); // '<i class="icon-hdd"></i> '; 7416 $text .= e107::getLanguage()->toNative($lang)."</span>"; 7417 return $text; 7418 7419 7420 } 7421 7422 7423 7424 // FIXME - use e_form::batchoptions(), nice way of building batch dropdown - news administration show_batch_options() 7425 7426 /** 7427 * @param array $options array of flags for copy, delete, url, featurebox, batch 7428 * @param array $customBatchOptions 7429 * @return string 7430 */ 7431 function renderBatch($options, $customBatchOptions=array()) 7432 { 7433 7434 $fields = $this->getController()->getFields(); 7435 7436 if(!varset($fields['checkboxes'])) 7437 { 7438 $mes = e107::getMessage(); 7439 $mes->add("Cannot display Batch drop-down as 'checkboxes' was not found in \$fields array.", E_MESSAGE_DEBUG); 7440 return ''; 7441 } 7442 7443 // FIX - don't show FB option if plugin not installed 7444 if(!e107::isInstalled('featurebox')) 7445 { 7446 $options['featurebox'] = false; 7447 } 7448 7449 // TODO - core ui-batch-option class!!! REMOVE INLINE STYLE! 7450 // XXX Quick Fix for styling - correct. 7451 $text = " 7452 <div id='admin-ui-list-batch' class='navbar navbar-inner left' > 7453 <div class='span6 col-md-6'>"; 7454 7455 $selectStart = "<div class='form-inline input-inline'> 7456 ".ADMIN_CHILD_ICON." 7457 <div class='input-group input-append'> 7458 ".$this->select_open('etrigger_batch', array('class' => 'tbox form-control input-large select batch e-autosubmit reset', 'id' => false))." 7459 ".$this->option(LAN_BATCH_LABEL_SELECTED, '', false); 7460 7461 $selectOpt = ''; 7462 7463 if(!$this->getController()->getTreeModel()->isEmpty()) 7464 { 7465 $selectOpt .= !empty($options['copy']) ? $this->option(LAN_COPY, 'copy', false, array('class' => 'ui-batch-option class', 'other' => 'style="padding-left: 15px"')) : ''; 7466 $selectOpt .= !empty($options['delete']) ? $this->option(LAN_DELETE, 'delete', false, array('class' => 'ui-batch-option class', 'other' => 'style="padding-left: 15px"')) : ''; 7467 $selectOpt .= !empty($options['export']) ? $this->option(LAN_UI_BATCH_EXPORT, 'export', false, array('class' => 'ui-batch-option class', 'other' => 'style="padding-left: 15px"')) : ''; 7468 $selectOpt .= !empty($options['url']) ? $this->option(LAN_UI_BATCH_CREATELINK, 'url', false, array('class' => 'ui-batch-option class', 'other' => 'style="padding-left: 15px"')) : ''; 7469 $selectOpt .= !empty($options['featurebox']) ? $this->option(LAN_PLUGIN_FEATUREBOX_BATCH, 'featurebox', false, array('class' => 'ui-batch-option class', 'other' => 'style="padding-left: 15px"')) : ''; 7470 7471 // if(!empty($parms['sef']) 7472 7473 7474 7475 if(!empty($customBatchOptions)) 7476 { 7477 foreach($customBatchOptions as $key=>$val) 7478 { 7479 7480 if(is_array($val)) 7481 { 7482 $selectOpt .= $this->optgroup_open($key); 7483 7484 foreach($val as $k=>$v) 7485 { 7486 $selectOpt .= $this->option($v, $k, false, array('class' => 'ui-batch-option class', 'other' => 'style="padding-left: 15px"')); 7487 } 7488 7489 $selectOpt .= $this->optgroup_close(); 7490 } 7491 else 7492 { 7493 $selectOpt .= $this->option($val, $key, false, array('class' => 'ui-batch-option class', 'other' => 'style="padding-left: 15px"')); 7494 } 7495 7496 7497 } 7498 7499 } 7500 7501 7502 $selectOpt .= $this->renderBatchFilter('batch'); 7503 7504 if(!empty($selectOpt)) 7505 { 7506 $text .= $selectStart; 7507 7508 $text .= $selectOpt; 7509 7510 $text .= $this->select_close(); 7511 7512 $text .= "<div class='input-group-btn input-append'> 7513 ".$this->admin_button('e__execute_batch', 'e__execute_batch', 'batch e-hide-if-js', LAN_GO, array('id' => false))." 7514 </div>"; 7515 $text .= "</div></div>"; 7516 } 7517 7518 $text .= "</div>"; 7519 7520 } 7521 7522 7523 $text .= " 7524 7525 <div id='admin-ui-list-total-records' class='span6 col-md-6 right'><span>".e107::getParser()->lanVars(LAN_UI_TOTAL_RECORDS,number_format($this->getController()->getTreeModel()->getTotal()))."</span></div> 7526 </div> 7527 "; 7528 7529 7530 return $text; 7531 } 7532 7533 7534 /** 7535 * Render Batch and Filter Dropdown options. 7536 * @param string $type 7537 * @param string $selected 7538 * @return string 7539 */ 7540 function renderBatchFilter($type='batch', $selected = '') // Common function used for both batches and filters. 7541 { 7542 $optdiz = array('batch' => LAN_BATCH_LABEL_PREFIX.' ', 'filter'=> LAN_FILTER_LABEL_PREFIX.' '); 7543 $table = $this->getController()->getTableName(); 7544 $text = ''; 7545 $textsingle = ''; 7546 7547 7548 $searchFieldOpts = array(); 7549 7550 $fieldList = $this->getController()->getFields(); 7551 7552 7553 7554 foreach($fieldList as $key=>$val) 7555 { 7556 if(!empty($val['search'])) 7557 { 7558 $searchFieldOpts["searchfield__".$key] = $val['title']; 7559 } 7560 7561 if(empty($val[$type])) // ie. filter = false or batch = false. 7562 { 7563 continue; 7564 } 7565 7566 $option = array(); 7567 $parms = vartrue($val['writeParms'], array()); 7568 if(is_string($parms)) parse_str($parms, $parms); 7569 7570 //Basic batch support for dropdown with multiple values. (comma separated) 7571 if(!empty($val['writeParms']['multiple']) && $val['type'] === 'dropdown' && !empty($val['writeParms']['optArray'])) 7572 { 7573 $val['type'] = 'comma'; 7574 $parms = $val['writeParms']['optArray']; 7575 } 7576 7577 7578 7579 switch($val['type']) 7580 { 7581 7582 case 'text'; 7583 7584 if(!empty($parms['sef'])) 7585 { 7586 $option['sefgen__'.$key.'__'.$parms['sef']] = LAN_GENERATE; 7587 } 7588 7589 $searchFieldOpts["searchfield__".$key] = $val['title']; 7590 7591 break; 7592 7593 7594 case 'number'; 7595 if($type === 'filter') 7596 { 7597 $option[$key.'___ISEMPTY_'] = LAN_UI_FILTER_IS_EMPTY; 7598 } 7599 7600 $searchFieldOpts["searchfield__".$key] = $val['title']; 7601 7602 break; 7603 7604 case 'textarea': 7605 case 'tags': 7606 $searchFieldOpts["searchfield__".$key] = $val['title']; 7607 break; 7608 7609 case 'bool': 7610 case 'boolean': //TODO modify description based on $val['parm] 7611 7612 // defaults 7613 $LAN_TRUE = LAN_ON; 7614 $LAN_FALSE = LAN_OFF; 7615 7616 if(varset($parms['label']) === 'yesno') 7617 { 7618 $LAN_TRUE = LAN_YES; 7619 $LAN_FALSE = LAN_NO; 7620 } 7621 7622 if(!empty($parms['enabled'])) 7623 { 7624 $LAN_TRUE = $parms['enabled']; 7625 } 7626 elseif(!empty($parms['true'])) 7627 { 7628 $LAN_TRUE = $parms['true']; 7629 } 7630 7631 if(!empty($parms['disabled'])) 7632 { 7633 $LAN_FALSE = $parms['disabled']; 7634 } 7635 elseif(!empty($parms['false'])) 7636 { 7637 $LAN_FALSE = $parms['false']; 7638 } 7639 7640 if(!empty($parms['reverse'])) // reverse true/false values; 7641 { 7642 $option['bool__'.$key.'__0'] = $LAN_TRUE; // see newspost.php : news_allow_comments for an example. 7643 $option['bool__'.$key.'__1'] = $LAN_FALSE; 7644 } 7645 else 7646 { 7647 $option['bool__'.$key.'__1'] = $LAN_TRUE; 7648 $option['bool__'.$key.'__0'] = $LAN_FALSE; 7649 } 7650 7651 if($type === 'batch') 7652 { 7653 $option['boolreverse__'.$key] = LAN_BOOL_REVERSE; 7654 } 7655 break; 7656 7657 case 'checkboxes': 7658 case 'comma': 7659 // TODO lan 7660 if(!isset($parms['__options'])) $parms['__options'] = array(); 7661 if(!is_array($parms['__options'])) parse_str($parms['__options'], $parms['__options']); 7662 $opts = $parms['__options']; 7663 unset($parms['__options']); //remove element options if any 7664 7665 $options = $parms ? $parms : array(); 7666 if(empty($options)) continue 2; 7667 7668 7669 if($type === 'batch') 7670 { 7671 $_option = array(); 7672 7673 if(isset($options['addAll'])) 7674 { 7675 $option['attach_all__'.$key] = vartrue($options['addAll'], "(".LAN_ADD_ALL.")"); 7676 unset($options['addAll']); 7677 } 7678 if(isset($options['clearAll'])) 7679 { 7680 $_option['deattach_all__'.$key] = vartrue($options['clearAll'], "(".LAN_CLEAR_ALL.")"); 7681 unset($options['clearAll']); 7682 } 7683 7684 if(vartrue($opts['simple'])) 7685 { 7686 foreach ($options as $value) 7687 { 7688 $option['attach__'.$key.'__'.$value] = LAN_ADD." ".$value; 7689 $_option['deattach__'.$key.'__'.$value] = LAN_REMOVE." ".$value; 7690 } 7691 } 7692 else 7693 { 7694 foreach ($options as $value => $label) 7695 { 7696 $option['attach__'.$key.'__'.$value] = LAN_ADD." ".$label; 7697 $_option['deattach__'.$key.'__'.$value] = LAN_REMOVE." ".$label; 7698 } 7699 } 7700 $option = array_merge($option, $_option); 7701 unset($_option); 7702 } 7703 else 7704 { 7705 unset($options['addAll'], $options['clearAll']); 7706 if(vartrue($opts['simple'])) 7707 { 7708 foreach($options as $k) 7709 { 7710 $option[$key.'__'.$k] = $k; 7711 } 7712 } 7713 else 7714 { 7715 foreach($options as $k => $name) 7716 { 7717 $option[$key.'__'.$k] = $name; 7718 } 7719 } 7720 7721 } 7722 break; 7723 7724 case 'templates': 7725 case 'layouts': 7726 $parms['raw'] = true; 7727 $val['writeParms'] = $parms; 7728 $tmp = $this->renderElement($key, '', $val); 7729 if(is_array($tmp)) 7730 { 7731 foreach ($tmp as $k => $name) 7732 { 7733 $option[$key.'__'.$k] = $name; 7734 } 7735 } 7736 break; 7737 7738 case 'dropdown': // use the array $parm; 7739 7740 7741 7742 7743 if(!empty($parms['optArray'])) 7744 { 7745 $fopts = $parms; 7746 $parms = $fopts['optArray']; 7747 unset($fopts['optArray']); 7748 $parms['__options'] = $fopts; 7749 } 7750 7751 7752 if(!is_array(varset($parms['__options']))) parse_str($parms['__options'], $parms['__options']); 7753 $opts = $parms['__options']; 7754 if(vartrue($opts['multiple']) && $type === 'batch') 7755 { 7756 // no batch support for multiple, should have some for filters soon 7757 continue 2; 7758 } 7759 7760 unset($parms['__options']); //remove element options if any 7761 7762 7763 7764 foreach($parms as $k => $name) 7765 { 7766 $option[$key.'__'.$k] = $name; 7767 } 7768 break; 7769 7770 case 'language': // full list of 7771 case 'lanlist': // use the array $parm; 7772 if(!is_array(varset($parms['__options']))) parse_str($parms['__options'], $parms['__options']); 7773 $opts = $parms['__options']; 7774 if(vartrue($opts['multiple'])) 7775 { 7776 // no batch support for multiple, should have some for filters soon 7777 continue 2; 7778 } 7779 $options = ($val['type'] === 'language') ? e107::getLanguage()->getList() : e107::getLanguage()->getLanSelectArray(); 7780 foreach($options as $code => $name) 7781 { 7782 $option[$key.'__'.$code] = $name; 7783 } 7784 break; 7785 7786 case 'datestamp': 7787 $tp = e107::getParser(); 7788 $dateFilters = array ( 7789 'hour' => LAN_UI_FILTER_PAST_HOUR, 7790 "day" => LAN_UI_FILTER_PAST_24_HOURS, 7791 "week" => LAN_UI_FILTER_PAST_WEEK, 7792 "month" => LAN_UI_FILTER_PAST_MONTH, 7793 "month3" => $tp->lanVars(LAN_UI_FILTER_PAST_XMONTHS,3), 7794 "month6" => $tp->lanVars(LAN_UI_FILTER_PAST_XMONTHS,6), 7795 "month9" => $tp->lanVars(LAN_UI_FILTER_PAST_XMONTHS,9), 7796 "year" => LAN_UI_FILTER_PAST_YEAR 7797 ); 7798 7799 $dateFiltersFuture = array ( 7800 'nhour' => LAN_UI_FILTER_NEXT_HOUR, 7801 "nday" => LAN_UI_FILTER_NEXT_24_HOURS, 7802 "nweek" => LAN_UI_FILTER_NEXT_WEEK, 7803 "nmonth" => LAN_UI_FILTER_NEXT_MONTH, 7804 "nmonth3" => $tp->lanVars(LAN_UI_FILTER_NEXT_XMONTHS,3), 7805 "nmonth6" => $tp->lanVars(LAN_UI_FILTER_NEXT_XMONTHS,6), 7806 "nmonth9" => $tp->lanVars(LAN_UI_FILTER_NEXT_XMONTHS,9), 7807 "nyear" => LAN_UI_FILTER_NEXT_YEAR 7808 ); 7809 7810 if($val['filter'] === 'future' ) 7811 { 7812 $dateFilters = $dateFiltersFuture; 7813 } 7814 7815 if($val['filter'] === 'both') 7816 { 7817 $dateFilters += $dateFiltersFuture; 7818 } 7819 7820 foreach($dateFilters as $k => $name) 7821 { 7822 $option['datestamp__'.$key.'__'.$k] = $name; 7823 // $option['bool__'.$key.'__0'] = LAN_NO; 7824 // $option[$key.'__'.$k] = $name; 7825 } 7826 break; 7827 7828 case 'userclass': 7829 $classes = e107::getUserClass()->uc_required_class_list(vartrue($parms['classlist'], 'public,nobody,guest,member,admin,main,classes')); 7830 foreach($classes as $k => $name) 7831 { 7832 $option[$key.'__'.$k] = $name; 7833 } 7834 break; 7835 case 'userclasses': 7836 $classes = e107::getUserClass()->uc_required_class_list(vartrue($parms['classlist'], 'public,nobody,guest,member,admin,main,classes')); 7837 $_option = array(); 7838 7839 if($type === 'batch') 7840 { 7841 foreach ($classes as $k => $v) 7842 { 7843 $option['ucadd__'.$key.'__'.$k] = LAN_ADD.' '.$v; 7844 $_option['ucremove__'.$key.'__'.$k] = LAN_REMOVE." ".$v; 7845 } 7846 $option['ucaddall__'.$key] = "(".LAN_ADD_ALL.")"; 7847 $_option['ucdelall__'.$key] = "(".LAN_CLEAR_ALL.")"; 7848 $option = array_merge($option, $_option); 7849 } 7850 else 7851 { 7852 foreach ($classes as $k => $v) 7853 { 7854 $option[$key.'__'.$k] = $v; 7855 } 7856 } 7857 7858 unset($_option); 7859 break; 7860 7861 case 'method': 7862 $method = $key; 7863 $list = call_user_func_array(array($this, $method), array('', $type, $parms)); 7864 7865 if(is_array($list)) 7866 { 7867 //check for single option 7868 if(isset($list['singleOption'])) 7869 { 7870 $textsingle .= $list['singleOption']; 7871 continue 2; 7872 } 7873 // non rendered options array 7874 foreach($list as $k => $name) 7875 { 7876 $option[$key.'__'.$k] = $name; 7877 } 7878 } 7879 elseif(!empty($list)) //optgroup, continue 7880 { 7881 $text .= $list; 7882 continue 2; 7883 } 7884 break; 7885 7886 case 'user': // TODO - User Filter 7887 7888 $sql = e107::getDb(); 7889 $field = $val['field']; 7890 7891 $query = "SELECT d.".$field.", u.user_name FROM #".$val['table']." AS d LEFT JOIN #user AS u ON d.".$field." = u.user_id GROUP BY d.".$field." ORDER BY u.user_name"; 7892 $row = $sql->retrieve($query,true); 7893 foreach($row as $data) 7894 { 7895 $k = $data[$field]; 7896 if($k == 0) 7897 { 7898 $option[$key.'__'.$k] = "(".LAN_ANONYMOUS.")"; 7899 } 7900 else 7901 { 7902 $option[$key.'__'.$k] = vartrue($data['user_name'],LAN_UNKNOWN); 7903 } 7904 7905 7906 } 7907 break; 7908 } 7909 7910 7911 7912 if(!empty($option)) 7913 { 7914 $text .= "\t".$this->optgroup_open($optdiz[$type].defset($val['title'], $val['title']), varset($disabled))."\n"; 7915 foreach($option as $okey=>$oval) 7916 { 7917 $text .= $this->option($oval, $okey, $selected == $okey)."\n"; 7918 } 7919 $text .= "\t".$this->optgroup_close()."\n"; 7920 } 7921 } 7922 7923 7924 if(!empty($searchFieldOpts)) 7925 { 7926 $text .= "\t".$this->optgroup_open(defset("LAN_UI_FILTER_SEARCH_IN_FIELD", "Search in Field"))."\n"; 7927 7928 foreach($searchFieldOpts as $key=>$val) 7929 { 7930 $text .= $this->option($val, $key, $selected == $key)."\n"; 7931 } 7932 7933 $text .= "\t".$this->optgroup_close()."\n"; 7934 } 7935 7936 7937 7938 return $textsingle.$text; 7939 7940 } 7941 7942 public function getElementId() 7943 { 7944 $controller = $this->getController(); 7945 $name = str_replace('_', '-', ($controller->getPluginName() === 'core' ? 'core-'.$controller->getTableName() : 'plugin-'.$controller->getPluginName())); 7946 return e107::getForm()->name2id($name); // prevent invalid ids. 7947 } 7948 7949 /** 7950 * @return e_admin_ui 7951 */ 7952 public function getController() 7953 { 7954 7955 return $this->_controller; 7956 } 7957} 7958 7959 7960 7961 7962 7963include_once(e107::coreTemplatePath('admin_icons')); 7964 7965/** 7966 * TODO: 7967 * 1. [DONE - a good start] move abstract peaces of code to the proper classes 7968 * 2. [DONE - at least for alpha release] remove duplicated code (e_form & e_admin_form_ui), refactoring 7969 * 3. make JS Manager handle Styles (.css files and inline CSS) 7970 * 4. [DONE] e_form is missing some methods used in e_admin_form_ui 7971 * 5. [DONE] date convert needs string-to-datestamp auto parsing, strptime() is the solution but needs support for 7972 * Windows and PHP < 5.1.0 - build custom strptime() function (php_compatibility_handler.php) on this - 7973 * http://sauron.lionel.free.fr/?page=php_lib_strptime (bad license so no copy/paste is allowed!) 7974 * 6. [DONE - read/writeParms introduced ] $fields[parms] mess - fix it, separate list/edit mode parms somehow 7975 * 7. clean up/document all object vars (e_admin_ui, e_admin_dispatcher) 7976 * 8. [DONE hopefully] clean up/document all parameters (get/setParm()) in controller and model classes 7977 * 9. [DONE] 'ip' field type - convert to human readable format while showing/editing record 7978 * 10. draggable (or not?) ordering (list view) 7979 * 11. [DONE] realtime search filter (typing text) - like downloads currently 7980 * 12. [DONE] autosubmit when 'filter' dropdown is changed (quick fix?) 7981 * 13. tablerender captions 7982 * 14. [DONE] textareas auto-height 7983 * 15. [DONE] multi JOIN table support (optional), aliases 7984 * 16. tabs support (create/edit view) 7985 * 17. tree list view (should handle cases like Site Links admin page) 7986 */ 7987 7988 7989