1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408
|
<?php
use LAM\PDF\PDFLabelValue;
use LAM\PDF\PDFTable;
use LAM\TYPES\ConfiguredType;
use function LAM\TYPES\getScopeFromTypeId;
use LAM\PDF\PDFImage;
/*
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2003 - 2024 Roland Gruber
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/**
* This is the parent class for all account modules.
*
* It implements the complete module interface and uses meta-data
* provided by the account modules for its functions.
*
* @package modules
* @author Roland Gruber
* @see baseModule
*/
/** PDF functions */
include_once('pdf.inc');
/**
* Parent class of all account modules.
* It implements the complete module interface and uses meta-data
* provided by the account modules for its functions.<br>
* <br>
* <b>Location and naming of modules</b><br>
* All LAM modules are placed in lib/modules/ and are named "<class name>.inc".
* E.g. if you create a new module and its class name is "qmail" then the filename would be "qmail.inc".
* The class name of a module must contain only a-z, A-Z, 0-9, -, and _.<br>
* <br>
* You can avoid to override many functions by using {@link get_metaData()}.<br>
* <br>
* All module classes should extend the baseModule class.
*
* @package modules
* @author Roland Gruber
*/
abstract class baseModule {
/**
* These attributes will be ignored by default if a new account is copied from an existing one.
*/
public const ATTRIBUTES_TO_IGNORE_ON_COPY_DEFAULT = ['uid', 'uidNumber', 'gid', 'gidNumber',
'cn', 'userpassword', 'sn', 'givenName', 'initials', 'telephoneNumber',
'homePhone', 'mobile', 'facsimileTelephoneNumber', 'pager', 'mail',
'employeeNumber', 'userCertificate;binary', 'userCertificate',
'homeDirectory', 'unixHomeDirectory', 'jpegPhoto'];
/** includes all meta data provided by the sub class */
protected $meta;
/** the account type of this module (user, group, host) */
private $scope;
/** configuration settings of all modules */
protected $moduleSettings;
/**
* self service profile with settings of all modules
* @var selfServiceProfile profile
*/
protected $selfServiceSettings;
/** name of parent accountContainer ($_SESSION[$base]) */
private $base;
/** contains all ldap attributes which should be written */
protected $attributes;
/** contains all ldap attributes which are loaded from ldap */
protected $orig;
/** contains all error messages of a module */
protected $messages;
/** if true, managed object classes are added when an account is created or loaded (default: true) */
protected $autoAddObjectClasses = true;
/**
* Creates a new base module class
*
* @param string $scope the account type (user, group, host)
*/
public function __construct($scope) {
$this->scope = $scope;
// load configuration
if ($this->can_manage() || ($scope === null)) {
if (isset($_SESSION['config'])) {
$this->moduleSettings = $_SESSION['config']->get_moduleSettings();
}
if (isset($_SESSION['selfServiceProfile'])) {
$this->selfServiceSettings = $_SESSION['selfServiceProfile'];
}
// initialize module
$this->load_Messages();
$this->meta = $this->get_metaData();
}
}
/**
* This function fills the $messages variable with output messages from this module.
*
* Calling this method requires the existence of an enclosing {@link accountContainer}.
*/
protected function load_Messages() {
}
/**
* Initializes the module after it became part of an {@link accountContainer}
*
* Calling this method requires the existence of an enclosing {@link accountContainer}.
*
* @param string $base the name of the {@link accountContainer} object ($_SESSION[$base])
*/
public function init($base) {
$this->base = $base;
$this->attributes = [];
$this->orig = [];
// add object classes if needed
$this->attributes['objectClass'] = [];
$this->orig['objectClass'] = [];
if ($this->autoAddObjectClasses === true) {
$objectClasses = $this->getManagedObjectClasses($this->getAccountContainer()->get_type()->getId());
for ($i = 0; $i < sizeof($objectClasses); $i++) {
if (!in_array($objectClasses[$i], $this->attributes['objectClass'])) {
$this->attributes['objectClass'][] = $objectClasses[$i];
}
}
}
}
/**
* This function loads the LDAP attributes when an account should be loaded.
*
* Calling this method requires the existence of an enclosing {@link accountContainer}.<br>
* <br>
* By default this method loads the object classes and accounts which are specified in {@link getManagedObjectClasses()}
* and {@link getManagedAttributes()}.
*
* @param array $attributes array like the array returned by get_ldap_attributes(dn of account) but without count indices
*/
public function load_attributes($attributes) {
$this->attributes = [];
// load object classes
if (isset($attributes['objectClass'])) {
$this->attributes['objectClass'] = $attributes['objectClass'];
$this->orig['objectClass'] = $attributes['objectClass'];
}
else {
$this->attributes['objectClass'] = [];
$this->orig['objectClass'] = [];
}
$typeId = $this->getAccountContainer()->get_type()->getId();
// add object classes if needed
if ($this->autoAddObjectClasses === true) {
$objectClasses = $this->getManagedObjectClasses($typeId);
for ($i = 0; $i < sizeof($objectClasses); $i++) {
if (!in_array($objectClasses[$i], $this->attributes['objectClass'])) {
$this->attributes['objectClass'][] = $objectClasses[$i];
}
}
}
// load attributes
$attributeNames = array_merge($this->getManagedAttributes($typeId), $this->getManagedHiddenAttributes($typeId));
$attributeNames = array_unique($attributeNames);
$attributeNames = array_values($attributeNames);
for ($i = 0; $i < sizeof($attributeNames); $i++) {
if (isset($attributes[$attributeNames[$i]])) {
$this->attributes[$attributeNames[$i]] = $attributes[$attributeNames[$i]];
$this->orig[$attributeNames[$i]] = $attributes[$attributeNames[$i]];
}
}
}
/**
* Loads the LDAP data from an account to copy.
*
* @param array $ldapAttributes LDAP attributes of copy
* @param array $attributesToIgnore list of attributes to ignore during load (defaults to self::ATTRIBUTES_TO_IGNORE_ON_COPY_DEFAULT)
*/
public function loadAttributesFromAccountCopy(array $ldapAttributes, array $attributesToIgnore = []): void {
if (empty($attributesToIgnore)) {
$attributesToIgnore = self::ATTRIBUTES_TO_IGNORE_ON_COPY_DEFAULT;
}
$typeId = $this->getAccountContainer()->get_type()->getId();
$validAttributeNames = array_merge($this->getManagedAttributes($typeId), $this->getManagedHiddenAttributes($typeId));
foreach ($ldapAttributes as $ldapAttribute => $values) {
if (in_array_ignore_case($ldapAttribute, $attributesToIgnore)) {
continue;
}
if (!in_array($ldapAttribute, $validAttributeNames)) {
continue;
}
$this->attributes[$ldapAttribute] = $values;
}
$objectClassesToCopy = $ldapAttributes['objectClass'];
$validObjectClasses = $this->getManagedObjectClasses($typeId);
foreach ($validObjectClasses as $validObjectClass) {
if (in_array_ignore_case($validObjectClass, $objectClassesToCopy) &&
(empty($this->attributes['objectClass']) || !in_array_ignore_case($validObjectClass, $this->attributes['objectClass']))) {
$this->attributes['objectClass'][] = $validObjectClass;
}
}
}
/**
* This function provides meta data which is interpreted by baseModule.
* Only subclasses will return real data.<br>
* <br>
* The aim of the meta data is to reduce the number
* of functions in the subclasses. All major data is centralized in one place.<br>
* <br>
* The returned array contains a list of key-value pairs for the different functions.<br>
* <ul>
*
* <li><b>{@link is_base_module()}</b><br>
* <br>
* <b>Key:</b> is_base<br>
* <b>Value:</b> boolean<br>
* <br>
* <b>Example:</b> "is_base" => true
* <br><br>
* </li>
*
* <li><b>{@link get_ldap_filter()}</b><br>
* <br>
* <b>Key:</b> ldap_filter<br>
* <b>Value:</b> array of filters<br>
* <br>
* <b>Example:</b> "ldap_filter" => array('or' => 'objectClass=posixAccount', 'and' => '(!(uid=*$))')
* <br><br>
* </li>
*
* <li><b>{@link getManagedObjectClasses()}</b><br>
* <br>
* <b>Key:</b> objectClasses<br>
* <b>Value:</b> array of object classes<br>
* <br>
* <b>Example:</b> "objectClasses" => array('posixAccount')
* <br><br>
* </li>
*
* <li><b>{@link getLDAPAliases()}</b><br>
* <br>
* <b>Key:</b> LDAPaliases<br>
* <b>Value:</b> array of aliases<br>
* <br>
* <b>Example:</b> "LDAPaliases" => array('commonName' => 'cn')
* <br><br>
* </li>
*
* <li><b>{@link get_RDNAttributes()}</b><br>
* <br>
* <b>Key:</b> RDN<br>
* <b>Value:</b> array of RDNs<br>
* <br>
* <b>Example:</b> "RDN" => array('uid' => 'normal', 'cn' => 'low')
* <br><br>
* </li>
*
* <li><b>{@link get_dependencies()}</b><br>
* <br>
* <b>Key:</b> dependencies<br>
* <b>Value:</b> array of dependencies<br>
* <br>
* <b>Example:</b> "dependencies" => array("depends" => array("posixAccount", array("qmail", "sendmail")), "conflicts" => array("exim"))
* <br><br>
* </li>
*
* <li><b>{@link get_profileOptions()}</b><br>
* <br>
* <b>Key:</b> profile_options<br>
* <b>Value:</b> array of profile options<br>
* <br>
* The syntax for the value array is the same as for the return value of get_profileOptions().
* <br><br>
* </li>
*
* <li><b>{@link check_profileOptions()}</b><br>
* <br>
* <b>Key:</b> profile_checks<br>
* <b>Value:</b> array of checks (array("optionName" => []))<br>
* <br>
* The "optionName" keys of the value array are the names of the option identifiers.<br>
* Each array element is an array itself containing these values:
* <ul>
* <li><b>type:</b> determines how to check input<br>
* Possible values:
* <ul>
* <li><b>regex:</b> check with regular expression from regex variable, case sensitive</li>
* <li><b>regex_i:</b> check with regular expression from regex variable, case insensitive</li>
* <li><b>int_greater:</b> integer value of cmp_name1 must be greater than the integer value from the option cmp_name2</li>
* <li><b>int_greaterOrEqual:</b> integer value of cmp_name1 must be greater or equal than the integer value from the option cmp_name2</li>
* </ul>
* </li>
* <li><b>error_message:</b> message that is displayed if input value was syntactically incorrect<br>
* error_message is an array to build StatusMessages (message type, message head, message text, additional variables)
* <li><b>regex:</b> regular expression string (only if type is regex/regex_i)</li>
* <li><b>cmp_name1:</b> name of first input variable that is used for comparison (only if type is int_greater/int_greaterOrEqual)</li>
* <li><b>cmp_name2:</b> name of second input variable that is used for comparison (only if type is int_greater/int_greaterOrEqual)</li>
* <li><b>required:</b> true or false, if this input field must be filled set to true (optional)
* <li><b>required_message:</b> message that is displayed if no input value was given (only if required == true)<br>
* required_message is an array to build StatusMessages (message type, message head, message text, additional variables)
* </li>
* </ul>
* <br><br>
* </li>
*
* <li><b>{@link load_profile()}</b><br>
* <br>
* <b>Key:</b> profile_mappings<br>
* <b>Value:</b> array('profile_identifier1' => 'LDAP_attribute1', 'profile_identifier2' => 'LDAP_attribute2')<br>
* <br>
* The mapped values are stored directly in $this->attributes.
* <br>
* <b>Example:</b> "profile_mappings" => array('inetOrgPerson_title' => 'title')
* <br><br>
* </li>
*
* <li><b>{@link get_configOptions()}</b><br>
* <br>
* <b>Key:</b> config_options<br>
* <b>Value:</b> array('user' => array, 'host' => array, 'all' => array)<br>
* <br>
* The values from 'all' are always returned, the other values only if they are inside the $scopes array.<br>
* The syntax for sub arrays is the same as for the return value of {@link get_configOptions()}.
* <br><br>
* </li>
*
* <li><b>{@link check_configOptions()}</b><br>
* <br>
* <b>Key:</b> config_checks<br>
* <b>Value:</b> array('user' => array, 'host' => 'array', 'all' => array)<br>
* <br>
* The values from 'all' are always used for checking, the other values only if they are inside the $scopes array.
* The syntax for sub arrays is the same as for {@link check_configOptions()}.
* <br><br>
* </li>
*
* <li><b>{@link get_uploadColumns()}</b><br>
* <br>
* <b>Key:</b> upload_columns<br>
* <b>Value:</b> array<br>
* <br>
* The syntax for array is the same as for the return value of {@link get_uploadColumns()}.
* <br><br>
* </li>
*
* <li><b>{@link get_uploadPreDepends()}</b><br>
* <br>
* <b>Key:</b> upload_preDepends<br>
* <b>Value:</b> array<br>
* <br>
* The syntax for array is the same as for the return value of {@link get_uploadPreDepends()}.
* <br><br>
* </li>
*
* <li><b>{@link getRequiredExtensions()}</b><br>
* <br>
* <b>Key:</b> extensions<br>
* <b>Value:</b> array of extension names<br>
* <br>
* <b>Example:</b> "extensions" => array('hash')
* <br><br>
* </li>
*
* <li><b>{@link get_help()}</b><br>
* <br>
* <b>Key:</b> help<br>
* <b>Value:</b> hashtable of help entries<br>
* <br>
* The hashtable is an array which maps help IDs to help entries.<br>
* <br>
* <b>Example:</b> 'help' => array('myEntry' => array('Headline' => 'This is the head line', 'Text' => 'Help content'))
* <br><br>
* </li>
*
* <li><b>{@link getSelfServiceSearchAttributes()}</b><br>
* <br>
* <b>Key:</b> selfServiceSearchAttributes<br>
* <b>Value:</b> array of attribute names<br>
* <br>
* <b>Example:</b> "selfServiceSearchAttributes" => array('uid')
* <br><br>
* </li>
*
* <li><b>{@link getSelfServiceFields()}</b><br>
* <br>
* <b>Key:</b> selfServiceFieldSettings<br>
* <b>Value:</b> array of self service fields<br>
* <br>
* <b>Example:</b> "selfServiceFieldSettings" => array('pwd' => 'Password')
* <br><br>
* </li>
*
* </ul>
* <b>Example:</b> return array("is_base" => true);
*
* @return array meta data
*/
public function get_metaData() {
return [];
}
/**
* Returns the account type of this module (user, group, host)
*
* @return string account type
*/
public function get_scope() {
return $this->scope;
}
/**
* Returns true if this module can manage accounts of the current type, otherwise false.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.
*
* @return boolean true if module fits
*
* @see baseModule::get_metaData()
*/
abstract public function can_manage();
/**
* Returns true if your module is a base module and otherwise false.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* Every account type needs exactly one base module. A base module manages a structural object class.
* E.g. the inetOrgPerson module is a base module since its object class is structural.
*
* @return boolean true if base module (defaults to false if no meta data is provided)
*
* @see baseModule::get_metaData()
*/
public function is_base_module() {
return (isset($this->meta['is_base']) && ($this->meta['is_base']));
}
/**
* Returns an LDAP filter for the account lists
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* Returns an array('or' => '...', 'and' => '...') that is used to build the LDAP filter. Usually, this is used to filter object classes.
* All "or" filter parts of the base modules are combined with OR and then combined with the "and" parts.<br>
* The resulting LDAP filter will look like this: (&(|(OR1)(OR2)(OR3))(AND1)(AND2)(AND3))<br>
* <br>
* <b>Example:</b> return array('or' => '(objectClass=posixAccount)', 'and' => '(!(uid=*$))')
*
* @param string $typeId account type id
* @return string LDAP filter
*
* @see baseModule::get_metaData()
*/
public function get_ldap_filter($typeId) {
return $this->meta['ldap_filter'] ?? "";
}
/**
* Returns an alias name for the module.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* This function returns a more descriptive string than the class name. Alias names are used for the buttons on the account pages and the module selection in the configuration wizard.<br>
* Please take care that your alias name is not too long. It may contain any character but should not include parts that may be interpreted by the browser (e.g. '<' or '>').
* If you use different aliases dependent on the account type please make sure that there is a general alias for unknown types.
*
* @return string alias name
*
* @see baseModule::get_metaData()
*/
public function get_alias() {
return $this->meta['alias'] ?? static::class;
}
/**
* Returns a hash array containing a list of possible LDAP attributes that can be used to form the RDN (Relative Distinguished Name).
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* The returned elements have this form: <attribute> => <priority>
* <br> <attribute> is the name of the LDAP attribute
* <br> <priority> defines the priority of the attribute (can be "low", "normal", "high")<br>
* <br>
* <b>Example:</b> return array('uid' => 'normal', 'cn' => 'low')
*
* @param string $typeId account type
* @return array list of attributes
*
* @see baseModule::get_metaData()
*/
public function get_RDNAttributes($typeId) {
return $this->meta['RDN'] ?? [];
}
/**
* This function returns a list with all depending and conflicting modules.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* The return value is an array with two sub arrays, "depends" and "conflicts".
* All values of the conflict array are string values with module names. All values of the depends
* array are either string values with module names or arrays which include only string values with
* module names.<br>
* If an element of the depends array is itself an array, this means that your module
* depends on one of these modules.<br>
* <br>
* <b>Example:</b> return array("depends" => array("posixAccount", array("qmail", "sendmail")), "conflicts" => array("exim"))
*
* @return array list of dependencies and conflicts
*
* @see baseModule::get_metaData()
*/
public function get_dependencies() {
return $this->meta['dependencies'] ?? ['depends' => [], 'conflicts' => []];
}
/**
* This function defines what attributes will be used in the account profiles and their appearance in the profile editor.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* The return value is an object implementing htmlElement.<br>
* The field name are used as keywords to load
* and save profiles. We recommend to use the module name as prefix for them
* (e.g. posixAccount_homeDirectory) to avoid naming conflicts.
*
* @param string $typeId type id (user, group, host, ...)
* @return htmlElement meta HTML object
*
* @see baseModule::get_metaData()
* @see htmlElement
*/
public function get_profileOptions($typeId) {
return $this->meta['profile_options'] ?? null;
}
/**
* Checks input values of account profiles.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* $options is an hash array (option name => value) that contains the user input.
* The option values are all arrays containing one or more elements.<br>
* If the input data is invalid the return value is an array that contains arrays
* to build StatusMessages (message type, message head, message text). If no errors occurred
* the function returns an empty array.
*
* @param array $options a hash array (name => value) containing the user input
* @param string $typeId type id (user, group, host)
* @return array list of error messages (array(type, title, text)) to generate StatusMessages, if any
*
* @see baseModule::get_metaData()
*/
public function check_profileOptions($options, $typeId) {
$errors = [];
if (isset($this->meta['profile_checks'])) {
foreach ($this->meta['profile_checks'] as $identifier => $check) {
// check if option is required
if (isset($check['required']) && $check['required']
&& (!isset($options[$identifier][0]) || ($options[$identifier][0] == ''))) {
$errors[] = $check['required_message'];
continue;
}
switch ($check['type']) {
// check by regular expression (from account.inc)
case "ext_preg":
// ignore empty fields
if (!empty($options[$identifier][0])
&& !get_preg($options[$identifier][0], $check['regex'])) {
$errors[] = $check['error_message'];
}
break;
// check by regular expression (case insensitive)
case 'regex_i':
// ignore empty fields
if (!empty($options[$identifier][0])
&& !preg_match('/' . $check['regex'] . '/i', $options[$identifier][0])) {
$errors[] = $check['error_message'];
}
break;
// check by regular expression (case sensitive)
case 'regex':
// ignore empty fields
if (!empty($options[$identifier][0])
&& !preg_match('/' . $check['regex'] . '/', $options[$identifier][0])) {
$errors[] = $check['error_message'];
}
break;
// check by integer comparison (greater)
case 'int_greater':
$val1 = $options[$check['cmp_name1']][0];
$val2 = $options[$check['cmp_name2']][0];
// ignore if both fields are empty
if (!(empty($val1) && empty($val2))
&& (($val1 == '') || ($val2 == '') || !(intval($val1) > intval($val2)))) {
$errors[] = $check['error_message'];
}
break;
// check by integer comparison (greater or equal)
case 'int_greaterOrEqual':
$val1 = $options[$check['cmp_name1']][0];
$val2 = $options[$check['cmp_name2']][0];
// ignore if both fields are empty
if (!(empty($val1) && empty($val2))
&& (($val1 == '') || ($val2 == '') || !(intval($val1) >= intval($val2)))) {
$errors[] = $check['error_message'];
}
break;
// print error message for invalid types
default:
StatusMessage("ERROR", "Unsupported type!", $check['type']);
break;
}
}
}
return $errors;
}
/**
* This function loads the values from an account profile to the module's internal data structures.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.
*
* @param array $profile hash array with profile values (identifier => value)
*
* @see baseModule::get_metaData()
*/
public function load_profile($profile) {
if (isset($this->meta['profile_mappings'])) {
$identifiers = array_keys($this->meta['profile_mappings']);
for ($i = 0; $i < sizeof($identifiers); $i++) {
if (isset($profile[$identifiers[$i]])) {
$this->attributes[$this->meta['profile_mappings'][$identifiers[$i]]] = $profile[$identifiers[$i]];
}
}
}
}
/**
* Returns a list of configuration options.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* The field names are used as keywords to load and save settings.
* We recommend to use the module name as prefix for them (e.g. posixAccount_homeDirectory) to avoid naming conflicts.
*
* @param array $scopes account types (user, group, host)
* @param array $allScopes list of all active account modules and their account type id (module => array(type id))
* @return mixed htmlElement or array of htmlElement
*
* @see baseModule::get_metaData()
* @see htmlElement
*/
public function get_configOptions($scopes, $allScopes) {
$return = [];
for ($i = 0; $i < sizeof($scopes); $i++) {
if (isset($this->meta['config_options'][$scopes[$i]])) {
if (is_array($this->meta['config_options'][$scopes[$i]])) {
$return = array_merge($return, $this->meta['config_options'][$scopes[$i]]);
}
elseif (isset($return[0]) && ($return[0] instanceof htmlTable) && ($this->meta['config_options'][$scopes[$i]] instanceof htmlTable)) {
$return[0]->mergeTableElements($this->meta['config_options'][$scopes[$i]]);
}
else {
$return[] = $this->meta['config_options'][$scopes[$i]];
}
}
}
if (isset($this->meta['config_options']['all'])) {
if (is_array($this->meta['config_options']['all'])) {
$return = array_merge($return, $this->meta['config_options']['all']);
}
elseif (isset($return[0]) && ($return[0] instanceof htmlTable) && ($this->meta['config_options']['all'] instanceof htmlTable)) {
$return[0]->mergeTableElements($this->meta['config_options']['all']);
}
else {
$return[] = $this->meta['config_options']['all'];
}
}
return $return;
}
/**
* Checks input values of module settings.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* If the input data is invalid the return value is an array that contains subarrays to build StatusMessages ('message type', 'message head', 'message text').
* <br>If no errors occurred the function returns an empty array.
*
* @param array $typeIds list of account type ids which are used
* @param array $options hash array (option name => value) that contains the input. The option values are all arrays containing one or more elements.
* @return array list of error messages
*
* @see baseModule::get_metaData()
*/
public function check_configOptions($typeIds, &$options) {
$messages = [];
// add checks that are independent of scope
$scopes = ['all'];
foreach ($typeIds as $typeId) {
$scopes[] = getScopeFromTypeId($typeId);
}
$scopes = array_unique($scopes);
foreach ($scopes as $scope) {
if (isset($this->meta['config_checks'][$scope]) && is_array($this->meta['config_checks'][$scope])) {
$identifiers = array_keys($this->meta['config_checks'][$scope]);
for ($i = 0; $i < sizeof($identifiers); $i++) {
// check if option is required
if (isset($this->meta['config_checks'][$scope][$identifiers[$i]]['required']) && ($this->meta['config_checks'][$scope][$identifiers[$i]]['required']) && ($options[$identifiers[$i]][0] == '')) {
$messages[] = $this->meta['config_checks'][$scope][$identifiers[$i]]['required_message'];
}
switch ($this->meta['config_checks'][$scope][$identifiers[$i]]['type']) {
// check by regular expression (from account.inc)
case "ext_preg":
// ignore empty fields
if ($options[$identifiers[$i]][0] == '') {
break;
}
if (!get_preg($options[$identifiers[$i]][0], $this->meta['config_checks'][$scope][$identifiers[$i]]['regex'])) {
$messages[] = $this->meta['config_checks'][$scope][$identifiers[$i]]['error_message'];
}
break;
// check by regular expression (case insensitive)
case "regex_i":
// ignore empty fields
if ($options[$identifiers[$i]][0] == '') {
break;
}
if (!preg_match('/' . $this->meta['config_checks'][$scope][$identifiers[$i]]['regex'] . '/i', $options[$identifiers[$i]][0])) {
$messages[] = $this->meta['config_checks'][$scope][$identifiers[$i]]['error_message'];
}
break;
// check by regular expression (case sensitive)
case "regex":
// ignore empty fields
if ($options[$identifiers[$i]][0] == '') {
break;
}
if (!preg_match('/' . $this->meta['config_checks'][$scope][$identifiers[$i]]['regex'] . '/', $options[$identifiers[$i]][0])) {
$messages[] = $this->meta['config_checks'][$scope][$identifiers[$i]]['error_message'];
}
break;
// check by integer comparison (greater)
case "int_greater":
// ignore if both fields are empty
if (($options[$this->meta['config_checks'][$scope][$identifiers[$i]]['cmp_name1']][0] == '') && ($options[$this->meta['config_checks'][$scope][$identifiers[$i]]['cmp_name2']][0] == '')) {
break;
}
// print error message if only one field is empty
if (($options[$this->meta['config_checks'][$scope][$identifiers[$i]]['cmp_name1']][0] == '') || ($options[$this->meta['config_checks'][$scope][$identifiers[$i]]['cmp_name2']][0] == '')) {
$messages[] = $this->meta['config_checks'][$scope][$identifiers[$i]]['error_message'];
break;
}
// compare
if (!(intval($options[$this->meta['config_checks'][$scope][$identifiers[$i]]['cmp_name1']][0]) > intval($options[$this->meta['config_checks'][$scope][$identifiers[$i]]['cmp_name2']][0]))) {
$messages[] = $this->meta['config_checks'][$scope][$identifiers[$i]]['error_message'];
}
break;
// check by integer comparison (greater or equal)
case "int_greaterOrEqual":
// ignore if both fields are empty
if (($options[$this->meta['config_checks'][$scope][$identifiers[$i]]['cmp_name1']][0] == '') && ($options[$this->meta['config_checks'][$scope][$identifiers[$i]]['cmp_name2']][0] == '')) {
break;
}
// print error message if only one field is empty
if (($options[$this->meta['config_checks'][$scope][$identifiers[$i]]['cmp_name1']][0] == '') || ($options[$this->meta['config_checks'][$scope][$identifiers[$i]]['cmp_name2']][0] == '')) {
$messages[] = $this->meta['config_checks'][$scope][$identifiers[$i]]['error_message'];
break;
}
// compare
if (!(intval($options[$this->meta['config_checks'][$scope][$identifiers[$i]]['cmp_name1']][0]) >= intval($options[$this->meta['config_checks'][$scope][$identifiers[$i]]['cmp_name2']][0]))) {
$messages[] = $this->meta['config_checks'][$scope][$identifiers[$i]]['error_message'];
}
break;
// print error message on undefined type
default:
StatusMessage("ERROR", "Unsupported type!", $this->meta['config_checks'][$scope][$identifiers[$i]]['type']);
break;
}
}
}
}
return $messages;
}
/**
* Returns a list of config options for LAM's main configuration.
*
* @param array $currentSettings current settings
* @return htmlElement[] config options
*/
public function getGlobalConfigOptions(array $currentSettings): array {
return [];
}
/**
* Checks the global config options.
*
* @param string[] $messages info messages can be added here
* @param string[] $errors error messages can be added here
* @param array $options config options
*/
public function checkGlobalConfigOptions(array &$options, array &$messages, array &$errors): void {
// to be implemented by modules if needed
}
/**
* Specifies if the module supports global cron job actions.
*
* @return bool supports cron
*/
public function supportsGlobalCronJob(): bool {
return false;
}
/**
* Runs any global cron actions.
*
* @param bool $isDryRun dry-run active
* @throws LAMException error during execution
*/
public function runGlobalCronActions(bool $isDryRun): void {
// needs to be implemented by submodule if needed
}
/**
* Returns a hashtable with all entries that may be printed out in the PDF.
*
* @param string $typeId type id (user, group, host)
* @return array PDF entries as key => label
*
* @see baseModule::get_metaData()
*/
public function get_pdfFields($typeId) {
return $this->meta['PDF_fields'] ?? [];
}
/**
* Returns the PDF entries for this module.
*
* @param array $pdfKeys list of PDF keys that are included in document
* @param string $typeId type id (user, group, host)
* @return PDFEntry[] list of key => PDFEntry
*/
public function get_pdfEntries($pdfKeys, $typeId) {
return [];
}
/**
* Adds a simple PDF entry to the given array.
*
* @param array $result result array (entry will be added here)
* @param String $name ID
* @param String $label label name
* @param String $attrName attribute name (default: =$name)
* @param String $delimiter delimiter if multiple attribute values exist (default: ", ")
*/
protected function addSimplePDFField(&$result, $name, $label, $attrName = null, $delimiter = ', ') {
if ($attrName == null) {
$attrName = $name;
}
$value = '';
if (isset($this->attributes[strtolower($attrName)][0])) {
$attrName = strtolower($attrName);
}
if (isset($this->attributes[$attrName][0])) {
natcasesort($this->attributes[$attrName]);
$value = implode($delimiter, $this->attributes[$attrName]);
$value = trim($value);
}
$result[static::class . '_' . $name][] = new PDFLabelValue($label, $value);
}
/**
* Adds a simple PDF entry with the given key and value.
*
* @param array $result result array (entry will be added here)
* @param String $name ID
* @param String $label label name
* @param string|array $value value as String or array
* @param String $delimiter delimiter if value is array (default: ", ")
*/
public function addPDFKeyValue(&$result, $name, $label, $value, $delimiter = ', ') {
if (is_array($value)) {
natcasesort($value);
$value = implode($delimiter, $value);
}
$result[static::class . '_' . $name][] = new PDFLabelValue($label, $value);
}
/**
* Adds a table entry to the PDF.
*
* @param array $result result array (entry will be added here)
* @param String $name ID
* @param PDFTable $table table
*/
public function addPDFTable(&$result, $name, $table) {
if (empty($table->rows)) {
return;
}
$result[static::class . '_' . $name][] = $table;
}
/**
* Adds an image to the PDF.
*
* @param array $result result array (entry will be added here)
* @param String $attrName attribute name
* @param PDFTable $table table
*/
public function addPDFImage(&$result, $attrName) {
if (isset($this->attributes[$attrName]) && (sizeof($this->attributes[$attrName]) > 0)) {
$result[static::class . '_' . $attrName][] = new PDFImage($this->attributes[$attrName][0]);
}
}
/**
* Returns an array containing all input columns for the file upload.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* This function returns an array which contains subarrays which represent an upload column.
* <b>Syntax of column arrays:</b>
* <br>
* <br> array(
* <br> string: name, // fixed non-translated name which is used as column name (should be of format: <module name>_<column name>)
* <br> string: description, // short descriptive name
* <br> string: help, // help ID
* <br> string: example, // example value
* <br> string: values, // possible input values (optional)
* <br> string: default, // default value (optional)
* <br> boolean: required // true, if user must set a value for this column
* <br> boolean: unique // true if all values of this column must be different values (optional, default: "false")
* <br> )
*
* @param array $selectedModules list of selected account modules
* @param ConfiguredType $type account type
* @return array column list
*
* @see baseModule::get_metaData()
*/
public function get_uploadColumns($selectedModules, &$type) {
return $this->meta['upload_columns'] ?? [];
}
/**
* Returns a list of module names which must be processed in building the account before this module.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* The named modules may not be active, LAM will check this automatically.
*
* @return array list of module names
*
* @see baseModule::get_metaData()
*/
public function get_uploadPreDepends() {
return $this->meta['upload_preDepends'] ?? [];
}
/**
* In this function the LDAP accounts are built.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* Returns an array which contains subarrays to generate StatusMessages if any errors occurred.
*
* @param array $rawAccounts the user input data, contains one subarray for each account.
* @param array $ids list of IDs for column position (e.g. "posixAccount_uid" => 5)
* @param array $partialAccounts list of hash arrays (name => value) which are later added to LDAP
* @param array $selectedModules list of selected account modules
* @param ConfiguredType $type account type
* @return array list of error messages if any
*/
public function build_uploadAccounts($rawAccounts, $ids, &$partialAccounts, $selectedModules, &$type) {
// must be implemented in sub modules
return [];
}
/**
* Maps simple upload fields directly to LDAP attribute values.
*
* @param array $rawAccounts the user input data, contains one subarray for each account.
* @param array $ids list of IDs for column position (e.g. "posixAccount_uid" => 5)
* @param array $partialAccounts list of hash arrays (name => value) which are later added to LDAP
* @param String $position current position in CSV
* @param String $colName column name
* @param String $attrName LDAP attribute name
* @param String|String[] $regex for get_preg() (e.g. 'ascii')
* @param array $message error message to add if regex does not match
* @param array $errors list of error messages if any
* @param String $regexSplit multiple values are separated and can be split with this preg_split expression (e.g. "/;[ ]?/")
*/
protected function mapSimpleUploadField(&$rawAccounts, &$ids, &$partialAccounts, $position, $colName, $attrName, $regex = null, $message = [], &$errors = [], $regexSplit = null) {
if (!isset($ids[$colName])) {
return;
}
if (!empty($rawAccounts[$position][$ids[$colName]])) {
$regexIDs = is_array($regex) ? $regex : [$regex];
// single value
if ($regexSplit == null) {
if (!empty($regex)) {
$this->checkUploadRegex($regexIDs, $rawAccounts[$position][$ids[$colName]], $message, $position, $errors);
}
$partialAccounts[$position][$attrName] = trim($rawAccounts[$position][$ids[$colName]]);
}
// multi-value
else {
$list = preg_split($regexSplit, trim($rawAccounts[$position][$ids[$colName]]));
$partialAccounts[$position][$attrName] = $list;
if (!empty($regex)) {
for ($x = 0; $x < sizeof($list); $x++) {
if (!$this->checkUploadRegex($regexIDs, $list[$x], $message, $position, $errors)) {
break;
}
}
}
}
}
}
/**
* Checks the upload value against a list of regular expressions.
*
* @param string[] $regexIDs regular expression IDs for get_preg()
* @param string $value value to check
* @param array $message error message array if not matching
* @param int $position upload position
* @param array $errors error messages
* @return value is ok
* @see get_preg()
*/
private function checkUploadRegex($regexIDs, $value, $message, $position, &$errors) {
$matched = false;
foreach ($regexIDs as $regexID) {
if (get_preg($value, $regexID)) {
$matched = true;
break;
}
}
if (!$matched) {
$errMsg = $message;
$errMsg[] = [$position];
$errors[] = $errMsg;
return false;
}
return true;
}
/**
* This function returns the help entry array for a specific help id.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* The result is an hashtable with the following keys:<br>
* <ul>
* <li><b>Headline (required)</b><br>
* The headline of this help entry. Can consist of any alphanumeric characters. No HTML/CSS elements are allowed.</li>
* <li><b>Text (required)</b><br>
* The text of the help entry which may contain any alphanumeric characters.</li>
* <li><b>SeeAlso (optional)</b><br>
* A reference to another related web site. It must be an array containing a field called "text" with the link text
* that should be displayed and a field called "link" which is the link target.</li>
* </ul>
* <br>
* <b>Example:</b><br>
* <br>
* array('Headline' => 'This is the head line', 'Text' => 'Help content', 'SeeAlso' => array('text' => 'LAM homepage', 'link' => 'http://www.ldap-account-manager.org/'))
*
* @param string $id The id string for the help entry needed.
* @return array The desired help entry.
*
* @see baseModule::get_metaData()
*/
public function get_help($id) {
if (isset($this->meta['help'][$id])) {
return $this->meta['help'][$id];
}
elseif (isset($this->meta['help'][$this->scope][$id])) {
return $this->meta['help'][$this->scope][$id];
}
else {
return false;
}
}
/**
* This function is used to check if this module page can be displayed.
*
* Calling this method requires the existence of an enclosing {@link accountContainer}.<br>
* <br>
* Your module might depend on input of other modules. This function determines if the user
* can change to your module page or not. The return value is true if your module accepts
* input, otherwise false.<br>
* This method's return value defaults to true.
*
* @return boolean true, if page can be displayed
*/
public function module_ready() {
return true;
}
/**
* This function is used to check if all settings for this module have been made.
*
* Calling this method requires the existence of an enclosing {@link accountContainer}.<br>
* <br>
* This function tells LAM if it can create/modify the LDAP account. If your module needs any
* additional input then set this to false. The user will be notified that your module needs
* more input.<br>
* This method's return value defaults to true.
*
* @return boolean true, if settings are complete
*/
public function module_complete() {
return true;
}
/**
* Controls if the module button the account page is visible and activated.
*
* Calling this method requires the existence of an enclosing {@link accountContainer}.<br>
* <br>
* <b>Possible return values:</b>
* <ul>
* <li><b>enabled:</b> button is visible and active</li>
* <li><b>disabled:</b> button is visible and deactivated (greyed)</li>
* <li><b>hidden:</b> no button will be shown</li>
* </ul>
*
* @return string status ("enabled", "disabled", "hidden")
*/
public function getButtonStatus() {
return "enabled";
}
/**
* Runs any actions that need to be done before an LDAP entry is created.
*
* @param array $attributes LDAP attributes of this entry (attributes are provided as reference, handle modifications of $attributes with care)
* @param ConfiguredType $type account type
* @return array array which contains status messages. Each entry is an array containing the status message parameters.
*/
public function doUploadPreActions($attributes, $type) {
return [];
}
/**
* This function is responsible to do additional tasks after the account has been created in LDAP (e.g. modifying group memberships, adding Quota etc..).
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* This function is called as long as the returned status is 'finished'. Please make sure
* that one function call lasts no longer than 3-4 seconds. Otherwise the upload may fail
* because the time limit is exceeded. You should not make more than one LDAP operation in
* each call.
*
* @param array $data array containing one account in each element
* @param array $ids maps the column names to keys for the sub arrays (array(<column_name> => <column number>))
* @param array $failed list of account numbers which could not be successfully uploaded to LDAP
* @param array $temp variable to store temporary data between two post actions
* @param array $accounts list of LDAP entries
* @param string[] $selectedModules selected account modules
* @param ConfiguredType $type account type
* @return array current status
* <br> array (
* <br> 'status' => 'finished' | 'inProgress' // defines if all operations are complete
* <br> 'progress' => 0..100 // the progress of the operations in percent
* <br> 'errors' => array // list of arrays which are used to generate StatusMessages
* <br> )
*/
public function doUploadPostActions(&$data, $ids, $failed, &$temp, &$accounts, $selectedModules, $type) {
return [
'status' => 'finished',
'progress' => 100,
'errors' => []
];
}
/**
* Returns a list of modifications which have to be made to the LDAP account.
*
* Calling this method requires the existence of an enclosing {@link accountContainer}.<br>
* <br>
*
* <br>This function returns an array with 3 entries:
* <br>array( DN1 ('add' => array($attr), 'remove' => array($attr), 'modify' => array($attr)), DN2 .... )
* <br>DN is the DN to change. It is possible to change several DNs (e.g. create a new user and add him
* to some groups via attribute memberUid)<br>
* <br><b>"add"</b> are attributes which have to be added to the LDAP entry
* <br><b>"remove"</b> are attributes which have to be removed from the LDAP entry
* <br><b>"modify"</b> are attributes which have to be modified in the LDAP entry
* <br><b>"notchanged"</b> are attributes which stay unchanged
* <br><b>"info"</b> values with informational value (e.g. to be used later by pre/postModify actions)
* <br>
* <br>This builds the required commands from $this-attributes and $this->orig.
*
* @return array list of modifications
*/
public function save_attributes() {
$this->getAccountContainer()->replaceWildcardsInArray($this->getWildcardTargetAttributeNames(), $this->attributes);
return $this->getAccountContainer()->save_module_attributes($this->attributes, $this->orig);
}
/**
* Allows the module to run commands before the LDAP entry is changed or created.
*
* Calling this method requires the existence of an enclosing {@link accountContainer}.<br>
* <br>
* The modification is aborted if an error message is returned.
*
* @param boolean $newAccount new account
* @param array $attributes LDAP attributes of this entry
* @return array array which contains status messages. Each entry is an array containing the status message parameters.
*/
public function preModifyActions($newAccount, &$attributes) {
return [];
}
/**
* Allows the module to run commands after the LDAP entry is changed or created.
*
* Calling this method requires the existence of an enclosing {@link accountContainer}.
*
* @param boolean $newAccount new account
* @param array $attributes LDAP attributes of this entry
* @return array array which contains status messages. Each entry is an array containing the status message parameters.
*/
public function postModifyActions($newAccount, $attributes) {
return [];
}
/**
* Allows the module to run commands before the LDAP entry is deleted.
*
* Calling this method requires the existence of an enclosing {@link accountContainer}.<br>
*
* @return array Array which contains status messages. Each entry is an array containing the status message parameters.
*/
public function preDeleteActions() {
return [];
}
/**
* Allows the module to run commands after the LDAP entry is deleted.
*
* Calling this method requires the existence of an enclosing {@link accountContainer}.
*
* @return array Array which contains status messages. Each entry is an array containing the status message parameters.
*/
public function postDeleteActions() {
return [];
}
/**
* This function returns an array with the same syntax as save_attributes().
*
* Calling this method requires the existence of an enclosing {@link accountContainer}.<br>
* <br>
* It allows additional LDAP changes when an account is deleted.
*
* @return array of LDAP operations, same as for save_attributes()
*/
public function delete_attributes(): array {
return [];
}
/**
* This function creates meta HTML code which will be displayed when an account should be deleted.
*
* Calling this method requires the existence of an enclosing {@link accountContainer}.<br>
* <br>
* This can be used to interact with the user, e.g. should the home directory be deleted? The output
* of all modules is displayed on a single page.
*
* @return htmlElement meta HTML object
* @see htmlElement
*/
public function display_html_delete() {
return null;
}
/**
* Defines if the LDAP entry has only virtual child entries. This is the case for e.g. LDAP views.
*
* @return boolean has only virtual children
*/
public function hasOnlyVirtualChildren() {
return false;
}
/**
* This function processes user input.
*
* Calling this method requires the existence of an enclosing {@link accountContainer}.<br>
* <br>
* It checks the user input and saves changes in the module's data structures.<br>
* <br>
* <b>Example:</b> return array(array('ERROR', 'Invalid input!', 'This is not allowed here.'));
*
* @return array Array which contains status messages. Each entry is an array containing the status message parameters.
*/
abstract public function process_attributes();
/**
* This function creates meta HTML code to display the module page.
*
* Calling this method requires the existence of an enclosing {@link accountContainer}.
*
* @return htmlElement meta HTML object
*
* @see htmlElement
*/
abstract public function display_html_attributes();
/**
* Adds a simple text input field to the given htmlResponsiveRow.
* The field name will be the same as the attribute name. There must also be a help entry with the attribute name as ID.
* A new line will also be added after this entry so multiple calls will show the fields one below the other.
*
* @param htmlResponsiveRow $container parent container
* @param String $attrName attribute name
* @param String $label label name
* @param boolean $required this is a required field (default false)
* @param integer $length field length
* @param boolean $isTextArea show as text area (default false)
* @param array $autoCompleteValues values for auto-completion
* @return mixed reference to htmlResponsiveInputField/htmlResponsiveInputTextarea
*/
protected function &addSimpleInputTextField(&$container, $attrName, $label, $required = false, $length = null, $isTextArea = false, $autoCompleteValues = null) {
$value = '';
if (isset($this->attributes[$attrName][0])) {
$value = $this->attributes[$attrName][0];
}
if ($isTextArea) {
$cols = 30;
if ($length != null) {
$cols = $length;
}
$input = new htmlResponsiveInputTextarea($attrName, $value, $cols, 3, $label, $attrName);
}
else {
$input = new htmlResponsiveInputField($label, $attrName, $value, $attrName);
if ($length != null) {
$input->setFieldSize($length);
}
if (!empty($autoCompleteValues)) {
$input->enableAutocompletion($autoCompleteValues);
}
}
$input->setRequired($required);
$container->add($input);
return $input;
}
/**
* Adds a simple read-only field to the given container.
*
* @param htmlResponsiveRow $container parent container
* @param String $attrName attribute name
* @param String $label field label
*/
protected function addSimpleReadOnlyField(&$container, $attrName, $label) {
$val = ' ';
if (!empty($this->attributes[$attrName][0])) {
$values = $this->attributes[$attrName];
array_map('htmlspecialchars', $values);
$val = implode('<br>', $values);
}
$labelBox = new htmlOutputText($label);
if (!empty($this->attributes[$attrName]) && (sizeof($this->attributes[$attrName]) > 1)) {
$labelBox->alignment = htmlElement::ALIGN_TOP;
}
$container->addLabel($labelBox);
$container->addField(new htmlOutputText($val, false));
}
/**
* Validates a text field.
* If validation is used then there must exist a message named [{attribute name}][0] (e.g. $this->messages['street'][0]).
*
* @param String $attrName attribute name
* @param array $errors errors array where to put validation errors
* @param bool $required value required
* @param String $validationID validation ID for function get_preg() (default: null, null means no validation)
*/
protected function processSimpleTextInput($attrName, &$errors, $required = false, $validationID = null) {
if ((!isset($_POST[$attrName]) || ($_POST[$attrName] === '')) && isset($this->attributes[$attrName][0])) {
unset($this->attributes[$attrName][0]);
if ($required) {
$errors[] = $this->messages[$attrName][0];
}
return;
}
$this->attributes[$attrName][0] = $_POST[$attrName] ?? '';
if ($required && (!isset($_POST[$attrName]) || ($_POST[$attrName] === ''))) {
$errors[] = $this->messages[$attrName][0];
}
elseif (($validationID !== null)
&& (isset($_POST[$attrName]) && ($_POST[$attrName] !== ''))
&& !get_preg($_POST[$attrName], $validationID)) {
$errors[] = $this->messages[$attrName][0];
}
}
/**
* Adds a text input field that may contain multiple values to the given htmlResponsiveRow.
* The field name will be the same as the attribute name plus a counting number (e.g. street_0).
* The last field will be followed by a button to add a new value. This is named add_{attribute name} (e.g. add_street).
* There must be a help entry with the attribute name as ID.
* A new line will also be added after this entry so multiple calls will show the fields one below the other.
*
* @param htmlResponsiveRow $container parent container
* @param String $attrName attribute name
* @param String $label label name
* @param boolean $required this is a required field (default false)
* @param integer $length field length
* @param boolean $isTextArea show as text area (default false)
* @param array $autoCompleteValues values for auto-completion
* @param integer $fieldSize field size
* @param array $htmlIDs reference to array where to add the generated HTML IDs of the input fields
* @param string $cssClasses additional CSS classes of input fields
*/
protected function addMultiValueInputTextField(&$container, $attrName, $label, $required = false, $length = null, $isTextArea = false,
$autoCompleteValues = null, $fieldSize = null, &$htmlIDs = null, $cssClasses = '') {
$values = [];
if (isset($this->attributes[$attrName][0])) {
$values = $this->attributes[$attrName];
}
if (sizeof($values) == 0) {
$values[] = '';
}
natcasesort($values);
$values = array_values($values);
if ($label !== null) {
$labelTextOut = new htmlOutputText($label, true, $required);
$labelTextOut->alignment = htmlElement::ALIGN_TOP;
$container->addLabel($labelTextOut);
}
$help = new htmlHelpLink($attrName);
$help->alignment = htmlElement::ALIGN_TOP;
$subContainer = new htmlTable();
$subContainer->setCSSClasses(['fullwidth']);
$subContainer->alignment = htmlElement::ALIGN_TOP;
for ($i = 0; $i < sizeof($values); $i++) {
if (!$isTextArea) {
$input = new htmlInputField($attrName . '_' . $i, $values[$i]);
$input->setAccessibilityLabel($label);
if (!empty($cssClasses)) {
$input->setCSSClasses([$cssClasses]);
}
if (!empty($length)) {
$input->setFieldMaxLength($length);
}
if (!empty($fieldSize)) {
$input->setFieldSize($fieldSize);
}
if (!empty($autoCompleteValues)) {
$input->enableAutocompletion($autoCompleteValues);
}
$subContainer->addElement($input);
}
else {
$cols = 30;
if ($length != null) {
$cols = $length;
}
$textArea = new htmlInputTextarea($attrName . '_' . $i, $values[$i], $cols, 3);
if (!empty($cssClasses)) {
$textArea->setCSSClasses([$cssClasses]);
}
$subContainer->addElement($textArea);
}
if (!empty($htmlIDs)) {
$htmlIDs[] = $attrName . '_' . $i;
}
if (!empty($values[$i])) {
$delButton = new htmlButton('del_' . $attrName . '_' . $i, 'del.svg', true);
$delButton->setCSSClasses(['noMarginSides']);
$delButton->setTitle(_('Delete'));
$subContainer->addElement($delButton);
}
if ($i == 0) {
$addButton = new htmlButton('add_' . $attrName, 'add.svg', true);
$addButton->setCSSClasses(['noMarginSides']);
$addButton->setTitle(_('Add'));
$subContainer->addElement($addButton);
$subContainer->addElement($help);
}
$subContainer->addNewLine();
}
if ($label !== null) {
$container->addField($subContainer);
}
else {
$container->add($subContainer);
}
}
/**
* Validates a multi-value text field.
* The input fields must be created with function addMultiValueInputTextField().
* If validation is used then there must exist a message named [{attribute name}][0] (e.g. $this->messages['street'][0]).
*
* @param String $attrName attribute name
* @param array $errors errors array where to put validation errors
* @param String $validationID validation ID for function get_preg() (default: null, null means no validation)
* @param bool $required the field is required (default: false)
*/
protected function processMultiValueInputTextField($attrName, &$errors, $validationID = null, $required = false) {
$counter = 0;
while (isset($_POST[$attrName . '_' . $counter])) {
$this->attributes[$attrName][$counter] = trim($_POST[$attrName . '_' . $counter]);
if (($this->attributes[$attrName][$counter] == '') || isset($_POST['del_' . $attrName . '_' . $counter])) {
unset($this->attributes[$attrName][$counter]);
}
elseif (($validationID != null) && ($this->attributes[$attrName][$counter] != '') && !get_preg($this->attributes[$attrName][$counter], $validationID)) {
$msg = $this->messages[$attrName][0];
if (sizeof($msg) < 3) {
$msg[] = htmlspecialchars($this->attributes[$attrName][$counter]);
}
else {
$msg[] = [htmlspecialchars($this->attributes[$attrName][$counter])];
}
$errors[] = $msg;
}
$counter++;
}
if (isset($_POST['add_' . $attrName])) {
$this->attributes[$attrName][] = '';
}
if (!empty($this->attributes[$attrName])) {
$this->attributes[$attrName] = array_values(array_unique($this->attributes[$attrName]));
}
if ($required && empty($this->attributes[$attrName])) {
$errors[] = $this->messages[$attrName][0];
}
}
/**
* Adds a select field type that may contain multiple values to the given htmlTable.
* The field name will be the same as the attribute name plus a counting number (e.g. street_0).
* The last field will be followed by a button to add a new value. This is named add_{attribute name} (e.g. add_street).
* There must be a help entry with the attribute name as ID.
* A new line will also be added after this entry so multiple calls will show the fields one below the other.
*
* @param htmlResponsiveRow $container parent container
* @param String $attrName attribute name
* @param String $label label name
* @param array $options options for the selects
* @param boolean $hasDescriptiveOptions has descriptive options
* @param boolean $required this is a required field (default false)
* @param integer $fieldSize field size
* @param array $htmlIDs reference to array where to add the generated HTML IDs of the input fields
*/
protected function addMultiValueSelectField(&$container, $attrName, $label, $options, $hasDescriptiveOptions = false,
$required = false, $fieldSize = 1, &$htmlIDs = null) {
$values = [];
if (isset($this->attributes[$attrName][0])) {
$values = $this->attributes[$attrName];
}
if (sizeof($values) == 0) {
$values[] = '';
}
natcasesort($values);
$values = array_values($values);
if ($label !== null) {
$labelTextOut = new htmlOutputText($label);
$labelTextOut->setMarkAsRequired($required);
$container->addLabel($labelTextOut);
}
$subContainer = new htmlTable();
$subContainer->alignment = htmlElement::ALIGN_TOP;
for ($i = 0; $i < sizeof($values); $i++) {
$input = new htmlSelect($attrName . '_' . $i, $options, [$values[$i]], $fieldSize);
$input->setHasDescriptiveElements($hasDescriptiveOptions);
$subContainer->addElement($input);
if (!empty($htmlIDs)) {
$htmlIDs[] = $attrName . '_' . $i;
}
if (!empty($values[$i])) {
$subContainer->addElement(new htmlButton('del_' . $attrName . '_' . $i, 'del.svg', true));
}
if ($i == 0) {
$subContainer->addElement(new htmlButton('add_' . $attrName, 'add.svg', true));
$subContainer->addElement(new htmlHelpLink($attrName));
}
$subContainer->addNewLine();
}
$container->addField($subContainer);
}
/**
* Validates a multi-value select field.
* The select fields must be created with function addMultiValueSelectField().
*
* @param String $attrName attribute name
*/
protected function processMultiValueSelectField($attrName) {
$counter = 0;
while (isset($_POST[$attrName . '_' . $counter])) {
$this->attributes[$attrName][$counter] = trim($_POST[$attrName . '_' . $counter]);
if (($this->attributes[$attrName][$counter] == '') || isset($_POST['del_' . $attrName . '_' . $counter])) {
unset($this->attributes[$attrName][$counter]);
}
$counter++;
}
if (isset($_POST['add_' . $attrName])) {
$this->attributes[$attrName][] = '';
}
$this->attributes[$attrName] = array_values(array_unique($this->attributes[$attrName]));
}
/**
* Adds an area with two multi-select fields with buttons to move items from right to left and vice-versa.
* The options of the selects must be presorted.
* <br>Names:
* <ul>
* <li>First select: $namePrefix_1
* <li>Second select: $namePrefix_2
* <li>Button move left: $namePrefix_left
* <li>Button move right: $namePrefix_right
* </ul>
*
* @param htmlResponsiveRow $container row
* @param string $labelFirst label of first select
* @param string $labelSecond label of second select
* @param string[] $optionsFirst options of first select ('label' => 'value')
* @param string[] $selectedFirst selected options of first select
* @param string[] $optionsSecond options of first select ('label' => 'value')
* @param string[] $selectedSecond selected options of second select
* @param string $namePrefix prefix for select field and button names
* @param bool $rightToLeftText sets the text direction in select to right to left
* @param bool $showFilter displays a live filter
*/
protected function addDoubleSelectionArea(&$container, $labelFirst, $labelSecond, $optionsFirst, $selectedFirst,
$optionsSecond, $selectedSecond, $namePrefix, $rightToLeftText = false, $showFilter = false) {
$forceTablet = false;
foreach ($optionsFirst as $label => $value) {
if (mb_strlen($label) > 75) {
$forceTablet = true;
break;
}
}
if (!$forceTablet) {
foreach ($optionsSecond as $label => $value) {
if (mb_strlen($label) > 75) {
$forceTablet = true;
break;
}
}
}
// first select
$firstRow = new htmlResponsiveRow();
$firstRow->add(new htmlOutputText($labelFirst));
$firstSelect = new htmlSelect($namePrefix . '_1', $optionsFirst, $selectedFirst, 15);
$firstSelect->setHasDescriptiveElements(true);
$firstSelect->setMultiSelect(true);
$firstSelect->setRightToLeftTextDirection($rightToLeftText);
$firstSelect->setSortElements(false);
$firstSelect->enableDynamicScrolling();
$firstRow->add($firstSelect);
if ($showFilter) {
$firstFilterGroup = new htmlGroup();
$firstFilterGroup->addElement(new htmlOutputText(_('Filter')));
$firstFilterInput = new htmlInputField($namePrefix . '_filterFirst');
$firstFilterInput->filterSelectBox($namePrefix . '_1');
$firstFilterGroup->addElement($firstFilterInput);
$firstRow->add($firstFilterGroup);
}
$firstContainerDesktop = ($forceTablet) ? 12 : 5;
$container->add($firstRow, 12, 12, $firstContainerDesktop);
// buttons
$buttonActiveDesktop = ($forceTablet) ? 0 : 12;
$buttonInactiveDesktop = ($forceTablet) ? 6 : 0;
$buttonRow = new htmlResponsiveRow();
$buttonRow->setCSSClasses(['text-center']);
$buttonRow->add(new htmlSpacer(null, '1rem'), 0, 0, $buttonActiveDesktop);
$buttonRow->add(new htmlButton($namePrefix . '_left', 'go-previous.svg', true), 0, 0, $buttonActiveDesktop);
$buttonRow->add(new htmlButton($namePrefix . '_left', 'up.svg', true), 6, 6, $buttonInactiveDesktop);
$buttonRow->add(new htmlButton($namePrefix . '_right', 'go-next.svg', true), 0, 0, $buttonActiveDesktop);
$buttonRow->add(new htmlButton($namePrefix . '_right', 'down.svg', true), 6, 6, $buttonInactiveDesktop);
$buttonRowDesktop = ($forceTablet) ? 12 : 1;
$container->add($buttonRow, 12, 12, $buttonRowDesktop);
// second select
$secondRow = new htmlResponsiveRow();
$secondRow->add(new htmlOutputText($labelSecond));
$secondSelect = new htmlSelect($namePrefix . '_2', $optionsSecond, $selectedSecond, 15);
$secondSelect->setHasDescriptiveElements(true);
$secondSelect->setMultiSelect(true);
$secondSelect->setRightToLeftTextDirection($rightToLeftText);
$secondSelect->setSortElements(false);
$secondSelect->enableDynamicScrolling();
$secondRow->add($secondSelect);
if ($showFilter) {
$secondFilterGroup = new htmlGroup();
$secondFilterGroup->addElement(new htmlOutputText(_('Filter')));
$secondFilterInput = new htmlInputField($namePrefix . '_filterSecond');
$secondFilterInput->filterSelectBox($namePrefix . '_2');
$secondFilterGroup->addElement($secondFilterInput);
$secondRow->add($secondFilterGroup);
}
$secondContainerDesktop = ($forceTablet) ? 12 : 6;
$container->add($secondRow, 12, 12, $secondContainerDesktop);
}
/**
* Adds a simple text input field for the self service.
* The field name will be the same as the class name plus "_" plus attribute name (e.g. posixAccount_cn).
*
* @param array $container array that is used as return value for getSelfServiceOptions()
* @param String $name attribute name (== field name)
* @param String $label label to display in front of input field
* @param array $fields list of active fields
* @param array $attributes attributes of LDAP account
* @param array $readOnlyFields list of read-only fields
* @param boolean $required field is required
* @param boolean $isTextArea display as text area
* @param string $attributeName attribute name (defaults to $name)
*/
protected function addSimpleSelfServiceTextField(&$container, $name, $label, &$fields, &$attributes, &$readOnlyFields, $required = false, $isTextArea = false, $attributeName = null) {
if (!in_array($name, $fields)) {
return;
}
$ldapAttrName = $attributeName ?? $name;
$value = '';
if (isset($attributes[$ldapAttrName][0])) {
$value = $attributes[$ldapAttrName][0];
}
if (!$isTextArea && !in_array($name, $readOnlyFields)) {
$field = new htmlInputField(static::class . '_' . $name, $value);
$field->setRequired($required);
$field->setFieldSize(null);
}
elseif ($isTextArea && !in_array($name, $readOnlyFields)) {
$field = new htmlInputTextarea(static::class . '_' . $name, $value, null, null);
}
else {
if (!$isTextArea) {
$field = new htmlOutputText($value);
}
else {
$value = htmlspecialchars($value);
$value = str_replace("\n", '<br>', $value);
$field = new htmlOutputText($value, false);
}
}
$row = new htmlResponsiveRow();
$fieldLabel = new htmlLabel(static::class . '_' . $name, $this->getSelfServiceLabel($name, $label));
$fieldLabel->setMarkAsRequired($required && !in_array($name, $readOnlyFields));
$row->addLabel($fieldLabel);
$row->addField($field);
$container[$name] = $row;
}
/**
* Checks the input value of a self service text field.
* The field name must be the same as the class name plus "_" plus attribute name (e.g. posixAccount_cn).
* If validation is used then there must exist a message named [{attribute name}][0] (e.g. $this->messages['street'][0]).
*
* @param array $container return value of checkSelfServiceOptions()
* @param String $name attribute name
* @param array $attributes LDAP attributes
* @param string $fields input fields
* @param array $readOnlyFields list of read-only fields
* @param String $validationID validation ID for get_preg()
* @param array $validationMessage validation message data (defaults to $this->messages[$name][0])
* @param array $requiredMessage message data when no value is set by user (no check if null)
* @param string $attributeName attribute name (defaults to $name)
*/
protected function checkSimpleSelfServiceTextField(&$container, $name, &$attributes, $fields, &$readOnlyFields,
$validationID = null, $validationMessage = null, $requiredMessage = null, $attributeName = null) {
if (in_array($name, $fields) && !in_array($name, $readOnlyFields)) {
$fieldName = static::class . '_' . $name;
$ldapAttrName = $attributeName ?? $name;
if (!empty($_POST[$fieldName])) {
if (($validationID != null) && !get_preg($_POST[$fieldName], $validationID)) {
if ($validationMessage !== null) {
$container['messages'][] = $validationMessage;
}
else {
$container['messages'][] = $this->messages[$name][0];
}
}
else {
if (isset($attributes[$ldapAttrName]) && ($attributes[$ldapAttrName][0] != $_POST[$fieldName])) {
$container['mod'][$ldapAttrName] = [$_POST[$fieldName]];
}
elseif (!isset($attributes[$ldapAttrName])) {
$container['add'][$ldapAttrName] = [$_POST[$fieldName]];
}
}
}
elseif (isset($attributes[$ldapAttrName])) {
if ($requiredMessage !== null) {
$container['messages'][] = $requiredMessage;
}
else {
$container['del'][$ldapAttrName] = $attributes[$ldapAttrName];
}
}
}
}
/**
* Adds a simple text input field for the self service.
* The field name will be the same as the class name plus "_" plus attribute name (e.g. posixAccount_cn).
*
* @param array $container array that is used as return value for getSelfServiceOptions()
* @param String $name attribute name (== field name)
* @param String $label label to display in front of input field
* @param array $fields list of active fields
* @param array $attributes attributes of LDAP account
* @param array $readOnlyFields list of read-only fields
* @param boolean $required field is required
* @param boolean $isTextArea display as text area
* @param string $attributeName attribute name (defaults to $name)
*/
protected function addMultiValueSelfServiceTextField(&$container, $name, $label, &$fields, &$attributes,
&$readOnlyFields, $required = false, $isTextArea = false, $attributeName = null) {
if (!in_array($name, $fields)) {
return;
}
$ldapAttrName = $attributeName ?? $name;
$values = [];
if (isset($attributes[$ldapAttrName][0])) {
$values = $attributes[$ldapAttrName];
}
$readOnly = in_array($name, $readOnlyFields);
$field = new htmlResponsiveRow();
$fieldNamePrefix = static::class . '_' . $name . '_';
if (!$readOnly) {
if (empty($values)) {
$values[] = '';
}
for ($i = 0; $i < sizeof($values); $i++) {
$fieldRow = new htmlResponsiveRow();
$value = $values[$i];
if (!$isTextArea) {
$inputField = new htmlInputField($fieldNamePrefix . $i, $value);
$inputField->setRequired($required);
$inputField->setFieldSize(null);
}
else {
$inputField = new htmlInputTextarea($fieldNamePrefix . $i, $value, 100, 3);
}
$inputField->setAccessibilityLabel($this->getSelfServiceLabel($name, $label));
$fieldRow->add($inputField, 9, 10);
$linkGroup = new htmlGroup();
if ($value !== '') {
$delLink = new htmlLink(null, '#', '../../graphics/del.svg');
$delLink->setOnClick('window.lam.selfservice.delMultiValue(\'' . $fieldNamePrefix . '\', this); return false;');
$delLink->setCSSClasses(['del-link icon']);
$delLink->setTitle(_('Delete'));
$linkGroup->addElement($delLink);
}
if ($i === (sizeof($values) - 1)) {
$addLink = new htmlLink(null, '#', '../../graphics/add.svg');
$addLink->setOnClick('window.lam.selfservice.addMultiValue(\'' . $fieldNamePrefix . '\', this); return false;');
$addLink->setCSSClasses(['add-link icon', 'margin-left5']);
$addLink->setTitle(_('Add'));
$linkGroup->addElement($addLink);
}
$fieldRow->add($linkGroup, 3, 2);
$field->add($fieldRow);
}
}
else {
foreach ($values as $value) {
if (!$isTextArea) {
$inputField = new htmlOutputText($value);
}
else {
$inputField = new htmlOutputText($value);
$inputField->setPreformatted();
}
$field->add($inputField);
}
}
$row = new htmlResponsiveRow();
$fieldLabel = new htmlLabel($fieldNamePrefix . '0', $this->getSelfServiceLabel($name, $label));
$fieldLabel->setMarkAsRequired($required);
$row->addLabel($fieldLabel);
$row->addField($field);
$container[$name] = $row;
}
/**
* Checks the input value of a self service multi-value text field.
* The field name must be the same as the class name plus "_" plus attribute name (e.g. posixAccount_cn).
* If validation is used then there must exist a message named [{attribute name}][0] (e.g. $this->messages['street'][0]).
*
* @param array $container return value of checkSelfServiceOptions()
* @param String $name attribute name
* @param array $attributes LDAP attributes
* @param string $fields input fields
* @param array $readOnlyFields list of read-only fields
* @param String $validationID validation ID for get_preg()
* @param array $validationMessage validation message data (defaults to $this->messages[$name][0])
* @param array $requiredMessage message data when no value is set by user (no check if null)
* @param string $attributeName attribute name (defaults to $name)
*/
protected function checkMultiValueSelfServiceTextField(&$container, $name, &$attributes, $fields, &$readOnlyFields,
$validationID = null, $validationMessage = null, $requiredMessage = null, $attributeName = null) {
if (in_array($name, $fields) && !in_array($name, $readOnlyFields)) {
$fieldName = static::class . '_' . $name;
$ldapAttrName = $attributeName ?? $name;
$valuesNew = [];
foreach ($_POST as $postKey => $postValue) {
if (!str_contains($postKey, $fieldName)) {
continue;
}
if ($postValue === '') {
continue;
}
if (($validationID != null) && !get_preg($postValue, $validationID)) {
if ($validationMessage !== null) {
$container['messages'][] = $validationMessage;
}
else {
$container['messages'][] = $this->messages[$name][0];
}
return;
}
$valuesNew[] = $postValue;
}
if (empty($valuesNew) && ($requiredMessage !== null)) {
$container['messages'][] = $requiredMessage;
}
$valuesOld = $attributes[$ldapAttrName] ?? [];
$intersect = array_intersect($valuesOld, $valuesNew);
if ((sizeof($valuesOld) != sizeof($valuesNew)) || (sizeof($intersect) != sizeof($valuesOld))) {
$container['mod'][$ldapAttrName] = $valuesNew;
}
}
}
/**
* Returns a list of managed object classes for this module.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* This is used to fix spelling errors in LDAP-Entries (e.g. if "posixACCOUNT" is read instead of "posixAccount" from LDAP).<br>
* <br>
* <b>Example:</b> return array('posixAccount')
*
* @param string $typeId type id (user, group, host)
* @return array list of object classes
*
* @see baseModule::get_metaData()
*/
public function getManagedObjectClasses($typeId) {
if (isset($this->meta['objectClasses']) && is_array($this->meta['objectClasses'])) {
return $this->meta['objectClasses'];
}
return [];
}
/**
* Returns a list of aliases for LDAP attributes.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* All alias attributes will be renamed to the given attribute names.
*
* @param string $typeId type id (user, group, host)
* @return array list of aliases like array("alias name" => "attribute name")
*
* @see baseModule::get_metaData()
*/
public function getLDAPAliases($typeId) {
if (isset($this->meta['LDAPaliases']) && is_array($this->meta['LDAPaliases'])) {
return $this->meta['LDAPaliases'];
}
return [];
}
/**
* Returns a list of LDAP attributes which are managed by this module.
* All attribute names will be renamed to match the given spelling.
*
* @param string $typeId type id (user, group, host)
* @return array list of attributes
*
* @see baseModule::get_metaData()
*/
public function getManagedAttributes($typeId) {
if (isset($this->meta['attributes']) && is_array($this->meta['attributes'])) {
return $this->meta['attributes'];
}
return [];
}
/**
* Returns a list of operational LDAP attributes which are managed by this module and need to be explicitly set for LDAP search.
*
* @param string $typeId account type id
* @return array list of hidden attributes
*
* @see baseModule::get_metaData()
*/
public function getManagedHiddenAttributes($typeId) {
if (isset($this->meta['hiddenAttributes']) && is_array($this->meta['hiddenAttributes'])) {
return $this->meta['hiddenAttributes'];
}
return [];
}
/**
* This function returns a list of PHP extensions (e.g. hash) which are needed by this module.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.
*
* @return array extensions
*
* @see baseModule::get_metaData()
*/
public function getRequiredExtensions() {
if (isset($this->meta['extensions']) && is_array($this->meta['extensions'])) {
return $this->meta['extensions'];
}
return [];
}
/**
* This function returns a list of possible LDAP attributes (e.g. uid, cn, ...) which can be used to search for LDAP objects.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.
*
* @return array attributes
*
* @see baseModule::get_metaData()
*/
public function getSelfServiceSearchAttributes() {
if (isset($this->meta['selfServiceSearchAttributes']) && is_array($this->meta['selfServiceSearchAttributes'])) {
return $this->meta['selfServiceSearchAttributes'];
}
return [];
}
/**
* Returns a list of possible input fields and their descriptions.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* <b>Format:</b> array(<field identifier> => <field description>)
*
* @return array fields
*
* @see baseModule::get_metaData()
*/
public function getSelfServiceFields() {
if (isset($this->meta['selfServiceFieldSettings']) && is_array($this->meta['selfServiceFieldSettings'])) {
return $this->meta['selfServiceFieldSettings'];
}
return [];
}
/**
* Returns if a given self service field can be set in read-only mode.
*
* @param String $fieldID field identifier
* @param selfServiceProfile $profile currently edited profile
* @return boolean may be set read-only
*/
public function canSelfServiceFieldBeReadOnly($fieldID, $profile) {
if (isset($this->meta['selfServiceReadOnlyFields']) && is_array($this->meta['selfServiceReadOnlyFields'])) {
return in_array($fieldID, $this->meta['selfServiceReadOnlyFields']);
}
return false;
}
/**
* Returns if a self service field can be relabeled.
*
* @param String $fieldID field ID
* @param selfServiceProfile $profile currently edited profile
* @return boolean may be relabeled
*/
public function canSelfServiceFieldBeRelabeled($fieldID, $profile) {
if (isset($this->meta['selfServiceNoRelabelFields']) && is_array($this->meta['selfServiceNoRelabelFields'])) {
return !in_array($fieldID, $this->meta['selfServiceNoRelabelFields']);
}
return true;
}
/**
* Returns the field label. This can be either the given default label or an override value from profile.
*
* @param String $fieldID field ID
* @param String $defaultLabel default label text
* @return String label
*/
protected function getSelfServiceLabel($fieldID, $defaultLabel) {
if (!$this->canSelfServiceFieldBeRelabeled($fieldID, $this->selfServiceSettings)) {
return $defaultLabel;
}
$key = static::class . '_' . $fieldID;
return empty($this->selfServiceSettings->relabelFields[$key]) ? $defaultLabel : $this->selfServiceSettings->relabelFields[$key];
}
/**
* Returns the meta HTML code for each input field.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* It is not possible to display help links.
*
* @param array $fields list of active fields
* @param array $attributes attributes of LDAP account
* @param boolean $passwordChangeOnly indicates that the user is only allowed to change his password and no LDAP content is readable
* @param array $readOnlyFields list of read-only fields
* @return array list of meta HTML elements (field name => htmlResponsiveRow)
*
* @see htmlElement
*/
public function getSelfServiceOptions($fields, $attributes, $passwordChangeOnly, $readOnlyFields) {
// this function must be overwritten by subclasses.
return [];
}
/**
* Checks if all input values are correct and returns the LDAP attributes which should be changed.
* <br>Return values:
* <br>messages: array of parameters to create status messages
* <br>add: array of attributes to add
* <br>del: array of attributes to remove
* <br>mod: array of attributes to modify
* <br>info: array of values with informational value (e.g. to be used later by pre/postModify actions)
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.
*
* @param array $fields input fields
* @param array $attributes LDAP attributes
* @param boolean $passwordChangeOnly indicates that the user is only allowed to change his password and no LDAP content is readable
* @param array $readOnlyFields list of read-only fields
* @return array messages and attributes (array('messages' => [], 'add' => array('mail' => array('test@test.com')), 'del' => [], 'mod' => [], 'info' => []))
*/
public function checkSelfServiceOptions($fields, $attributes, $passwordChangeOnly, $readOnlyFields) {
return ['messages' => [], 'add' => [], 'del' => [], 'mod' => [], 'info' => []];
}
/**
* Returns a list of self service configuration settings.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* The name attributes are used as keywords to load
* and save settings. We recommend to use the module name as prefix for them
* (e.g. posixAccount_homeDirectory) to avoid naming conflicts.
*
* @param selfServiceProfile $profile currently edited profile
* @return htmlElement meta HTML object
*
* @see baseModule::get_metaData()
* @see htmlElement
*/
public function getSelfServiceSettings($profile) {
return $this->meta['selfServiceSettings'] ?? null;
}
/**
* Checks if the self service settings are valid.
*
* Calling this method does not require the existence of an enclosing {@link accountContainer}.<br>
* <br>
* If the input data is invalid the return value is an array that contains arrays
* to build StatusMessages (message type, message head, message text). If no errors
* occurred the function returns an empty array.
*
* @param array $options hash array (option name => value) that contains the input. The option values are all arrays containing one or more elements.
* @param selfServiceProfile $profile self service profile
* @return array error messages
*/
public function checkSelfServiceSettings(&$options, &$profile) {
// needs to be implemented by the subclasses, if needed
return [];
}
/**
* Allows the module to run commands before the LDAP entry is changed or created.
*
* An error message should be printed if the function returns false.
*
* @param boolean $newAccount is new account or existing one
* @param array $attributes LDAP attributes of this entry
* @return boolean true, if no problems occurred
*/
public function preModifySelfService($newAccount, $attributes) {
return true;
}
/**
* Allows the module to run commands after the LDAP entry is changed or created.
*
* @param boolean $newAccount is new account or existing one
* @param array $attributes LDAP attributes of this entry
* @return boolean true, if no problems occurred
*/
public function postModifySelfService($newAccount, $attributes) {
return true;
}
/**
* This allows modules to create a link to a module specific page
* for the self service.
* The link is shown on the login page of the self service. You
* can use this to provide e.g. a page to reset passwords.
*
* @param array $settings self service settings
* @return String link text (null if no special page used)
*/
public function getLinkToSpecialSelfServicePage($settings) {
return null;
}
/**
* This function creates meta HTML code to display the module specific page
* for the self service.
*
* @param selfServiceProfile $profile self service settings
* @return htmlElement meta HTML object
*
* @see htmlElement
*/
public function displaySpecialSelfServicePage($profile) {
return null;
}
/**
* Returns the {@link accountContainer} object.
*
* @return ?accountContainer accountContainer object
*
* @see accountContainer
*/
protected function getAccountContainer(): ?accountContainer {
if (isset($this->base) && isset($_SESSION[$this->base])) {
return $_SESSION[$this->base];
}
else {
return null;
}
}
/**
* Returns the LDAP attributes which are managed in this module.
*
* @return array attributes
*/
public function getAttributes() {
return $this->attributes;
}
/**
* Returns the LDAP attributes which are managed in this module (with unchanged values).
*
* @return array attributes
*/
public function getOriginalAttributes() {
return $this->orig;
}
/**
* Returns the path to the module icon.
* The path must be relative to graphics (e.g. key.svg) or a URL (/icons/icon.svg or http://server/icon.svg).
* You can also set $this->meta['icon']. The preferred size is 32x32px, format SVG.
*
* @return string icon
*
* @see baseModule::get_metaData()
*/
public function getIcon(): string {
if (!empty($this->meta['icon']) && is_string($this->meta['icon'])) {
return $this->meta['icon'];
}
return 'configure.svg';
}
/**
* Manages AJAX requests.
* This function may be called with or without an account container.
*/
public function handleAjaxRequest() {
// modules that use AJAX need to implement this function
}
/**
* Specifies if this module supports the LAM admin interface.
* The LAM admin interface are the pages that allow to manage e.g. users and groups.
* In contrast there is also the LAM self service interface. Most modules support
* the admin interface.
*
* @return boolean support admin interface
*/
public function supportsAdminInterface() {
return true;
}
/**
* Returns a list of jobs that can be run.
*
* @param LAMConfig $config configuration
*/
public function getSupportedJobs(&$config) {
return [];
}
// helper functions
/**
* Returns if the given configuration option is set.
* This function returns false if the configuration options cannot be read.
*
* @param String $optionName name of the option
* @param boolean $default default value if config option is not set at all (default: false)
* @return boolean true if option is set
*/
protected function isBooleanConfigOptionSet($optionName, $default = false) {
// abort if configuration is not available
if (!isset($this->moduleSettings) || !is_array($this->moduleSettings) || !isset($this->moduleSettings[$optionName][0])) {
return $default;
}
return ($this->moduleSettings[$optionName][0] == 'true');
}
/**
* Returns a list of wildcards that can be replaced in input fields.
* E.g. "$firstname" is replaced with "givenName" attribute value.
*
* @return array replacements as wildcard => value
*/
public function getWildCardReplacements() {
return [];
}
/**
* Returns a list of attribute names that may contain wildcards.
*
* @return array list of attribute names
*/
public function getWildcardTargetAttributeNames(): array {
return [];
}
/**
* Returns a callable if there should be a custom filtering for the given attribute name.
*
* @param string $attributeName attribute name
* @return callable|null custom function for filtering (?array $values, ?string $filterValue)
*/
public function getListFilterFunction(string $attributeName): ?callable {
return null;
}
/**
* Returns a callable if there should be a custom display for the given attribute name.
*
* @param string $attributeName attribute name
* @return callable|null custom function for rendering (array $entry, string $attribute)
*/
public function getListRenderFunction(string $attributeName): ?callable {
return null;
}
/**
* Returns a list of attribute descriptions for the account list.
*
* @param ConfiguredType $type type
* @return array attribute name => description label
*/
public function getListAttributeDescriptions(ConfiguredType $type): array {
return [];
}
}
|