diff --git a/eq.lib.php b/eq.lib.php index c58add869..7fc7decca 100644 --- a/eq.lib.php +++ b/eq.lib.php @@ -1,10 +1,11 @@ +* Some Rights Reserved, The eQual Framework, 2010-2024 +* Original Author: Cedric Francoys +* License: GNU LGPL 3 license * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -870,7 +871,7 @@ public static function announce(array $announcement) { $access = $container->get('access'); if(!$access->isRequestCompliant($user_id, $request->getHeaders()->getIpAddress())) { Reporter::errorHandler(EQ_REPORT_SYSTEM, "AAA::".json_encode(['type' => 'policy', 'status' => 'denied'])); - throw new Exception("Request rejected by Security Policies", EQ_ERROR_NOT_ALLOWED); + throw new \Exception("Request rejected by Security Policies", EQ_ERROR_NOT_ALLOWED); } else { Reporter::errorHandler(EQ_REPORT_SYSTEM, "AAA::".json_encode(['type' => 'policy', 'status' => 'accepted', 'policy_id' => $access->getComplyingPolicyId()])); diff --git a/lib/equal/access/AccessController.class.php b/lib/equal/access/AccessController.class.php index 717eb2dd6..314ec97e8 100644 --- a/lib/equal/access/AccessController.class.php +++ b/lib/equal/access/AccessController.class.php @@ -12,6 +12,7 @@ use core\security\SecurityPolicy; use core\security\SecurityPolicyRule; use core\security\SecurityPolicyRuleValue; +use core\setting\Setting; use equal\orm\ObjectManager; use equal\organic\Service; use equal\services\Container; @@ -731,7 +732,7 @@ public function isRequestCompliant($user_id, $ip_address) { $result = true; $time = time(); - // fetch policies: request must be compliant with at least one policy. + // fetch security policies: request must be compliant with at least one policy. /** @var \equal\orm\ObjectManager */ $orm = $this->container->get('orm'); $security_policies_ids = $orm->search(SecurityPolicy::getType(), [['is_active', '=', true]]); @@ -826,6 +827,11 @@ private function validateTimeRange($time, $pattern) { list($hours, $minutes) = explode(':', date('H:i', $time)); $time_of_day = ($hours * 3600) + ($minutes * 60); + // retrieve timezone offset between local timezone and UTC (times in settings use local time) + $tz = new \DateTimeZone(Setting::get_value('core', 'locale', 'time_zone', 'Europe/Brussels')); + // apply timezone offset, in seconds to apply, depending on the date of the given date (based on received timestamp) + $time_of_day += $tz->getOffset(new \DateTime('@'.$time)); + $map_days = ['sun' => 0, 'mon' => 1, 'tue' => 2, 'wed' => 3, 'thu' => 4, 'fri' => 5, 'sat' => 6]; $day_of_week = strtolower(date('D', $time)); diff --git a/lib/equal/data/DataGenerator.class.php b/lib/equal/data/DataGenerator.class.php new file mode 100644 index 000000000..5df0c40ed --- /dev/null +++ b/lib/equal/data/DataGenerator.class.php @@ -0,0 +1,835 @@ + + Some Rights Reserved, Cedric Francoys, 2010-2024 + Licensed under GNU LGPL 3 license +*/ + +namespace equal\data; + +use equal\orm\UsageFactory; + +class DataGenerator { + + /** + * @param string $field Field name. + * @param string $field_descriptor Field descriptor. + * @param string $lang Lang code of the language in which the value must be generated (for multilang fields). + * @return array|bool|float|int|mixed|string|null + */ + public static function generateFromField(string $field, array $field_descriptor, string $lang = null) { + if(isset($field_descriptor['usage'])) { + try { + $usage = UsageFactory::create($field_descriptor['usage']); + return $usage->generateRandomValue(); + } + catch(\Exception $e) { + // Usage problem + } + } + + switch($field) { + case 'username': + return self::username(); + case 'firstname': + return self::firstname($lang); + case 'lastname': + return self::lastname($lang); + case 'fullname': + return self::fullname($lang); + case 'legal_name': + return self::legalName($lang); + case 'address_street': + return self::addressStreet($lang); + case 'address_zip': + return self::addressZip(); + case 'address_city': + return self::addressCity($lang); + case 'address_country': + return self::addressCountry($lang); + case 'address': + return self::address($lang); + } + + switch($field_descriptor['type']) { + case 'string': + if(!empty($field_descriptor['selection'])) { + if(isset($field_descriptor['selection'][0])) { + $values = array_values($field_descriptor['selection']); + } + else { + $values = array_keys($field_descriptor['selection']); + } + return $values[array_rand($values)]; + } + elseif(isset($field_descriptor['default'])) { + return $field_descriptor['default']; + } + return self::plainText(); + case 'boolean': + return self::boolean(); + case 'integer': + return self::integerByLength(9); + case 'float': + return self::realNumberByLength(9, 2); + } + + return null; + } + + public static function plainText($min = 0, $max = 255): string { + $words = [ + 'lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', + 'adipiscing', 'elit', 'sed', 'do', 'eiusmod', 'tempor', + 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', + 'aliqua', 'ut', 'enim', 'ad', 'minim', 'veniam', 'quis', + 'nostrud', 'exercitation', 'ullamco', 'laboris', 'nisi', + 'ut', 'aliquip', 'ex', 'ea', 'commodo', 'consequat' + ]; + + $generate_sentence = function() use ($words) { + $sentence_length = mt_rand(6, 12); + $sentence = []; + for ($i = 0; $i < $sentence_length; $i++) { + $sentence[] = $words[array_rand($words)]; + } + return ucfirst(implode(' ', $sentence)) . '.'; + }; + + $random_length = mt_rand($min, $max); + $random_text = ''; + + while (strlen($random_text) < $random_length) { + $paragraph_length = mt_rand(3, 7); + $paragraph = ''; + for ($i = 0; $i < $paragraph_length; $i++) { + $paragraph .= $generate_sentence() . ' '; + } + $random_text .= trim($paragraph) . "\n\n"; + } + + return trim(substr($random_text, 0, $random_length)); + } + + public static function boolean($probability = 0.5): bool { + $probability = max(0, min(1, $probability)); + + return mt_rand() / mt_getrandmax() < $probability; + } + + public static function integerByLength(int $length): int { + $min = (pow(10, $length) - 1) * -1; + $max = pow(10, $length) - 1; + + return mt_rand($min, $max); + } + + public static function integer(int $min, int $max): int { + return mt_rand($min, $max); + } + + public static function realNumberByLength(int $precision, int $scale): float { + $max_int_part = pow(10, $precision) - 1; + $min_int_part = -$max_int_part; + + $int_part = mt_rand($min_int_part, $max_int_part); + + $fractional_part = mt_rand(0, pow(10, $scale) - 1) / pow(10, $scale); + + $random_float = $int_part + $fractional_part; + + return round($random_float, $scale); + } + + public static function realNumber(float $min, float $max, int $decimals): float { + $scale = pow(10, $decimals); + + return mt_rand($min * $scale, $max * $scale) / $scale; + } + + public static function hexadecimal(int $length): string { + $num_bytes = ceil($length / 2); + $random_bytes = random_bytes($num_bytes); + $hexadecimal_string = bin2hex($random_bytes); + + return substr($hexadecimal_string, 0, $length); + } + + public static function email(): string { + $domains = [ + 'example.com', 'test.com', 'demo.com', 'sample.org', 'mywebsite.net', + 'mail.com', 'webmail.org', 'inbox.net', 'mailservice.io', 'emailprovider.co', + 'messaging.com', 'mailbox.org', 'fastmail.com', 'corpmail.net', 'freemail.org', + 'onlinemail.io', 'postbox.com', 'securemail.net', 'cloudmail.org', 'hostmail.co', + 'netmail.com', 'simplemail.io', 'yourmail.net', 'webservice.org', 'globalmail.com' + ]; + + $characters = '0123456789abcdefghijklmnopqrstuvwxyz'; + $username_length = mt_rand(5, 10); + $username = ''; + + for ($i = 0; $i < $username_length; $i++) { + $username .= $characters[mt_rand(0, strlen($characters) - 1)]; + } + + $domain = $domains[array_rand($domains)]; + + return $username . '@' . $domain; + } + + public static function phoneNumberE164(): string { + $country_codes = [ + '+1', '+7', '+27', '+31', '+32', '+33', '+34', '+352', '+39', '+44', '+46', + '+47', '+48', '+49', '+55', '+61', '+64', '+81', '+86', '+90', '+91', '+972' + ]; + + $country_code = $country_codes[array_rand($country_codes)]; + + $number_length = 15 - strlen($country_code); + $number = ''; + for ($i = 0; $i < $number_length; $i++) { + $number .= mt_rand(0, 9); + } + + return $country_code . $number; + } + + public static function time(): string { + $hours = str_pad(mt_rand(0, 23), 2, '0', STR_PAD_LEFT); + $minutes = str_pad(mt_rand(0, 59), 2, '0', STR_PAD_LEFT); + $seconds = str_pad(mt_rand(0, 59), 2, '0', STR_PAD_LEFT); + + return $hours . ':' . $minutes . ':' . $seconds; + } + + public static function relativeUrl(): string { + $depth = mt_rand(1, 5); + + $generateRandomString = function($length) { + $characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; + $random_string = ''; + for ($i = 0; $i < $length; $i++) { + $random_string .= $characters[mt_rand(0, strlen($characters) - 1)]; + } + return $random_string; + }; + + $url_path = ''; + for ($i = 0; $i < $depth; $i++) { + $url_path .= '/' . $generateRandomString(6); + } + + return $url_path; + } + + public static function url($protocol = null): string { + $protocols = ['http', 'https', 'ldap', 'dns', 'ftp']; + + if(is_null($protocol)) { + $protocol = $protocols[array_rand($protocols)]; + } + + $domain_length = mt_rand(3, 10); + $path_depth = mt_rand(0, 5); + + // Helper function to generate a random string of given length + $generateRandomString = function($length) { + $characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; + $random_string = ''; + for ($i = 0; $i < $length; $i++) { + $random_string .= $characters[random_int(0, strlen($characters) - 1)]; + } + return $random_string; + }; + + // Generate domain + $domain = $generateRandomString($domain_length) . '.' . $generateRandomString(3); // e.g., 'example.com' + + // Generate path + $path = ''; + for ($i = 0; $i < $path_depth; $i++) { + $path .= '/' . $generateRandomString(6); + } + + return $protocol . '://' . $domain . $path; + } + + public static function urlTel(): string { + return 'tel:' . self::phoneNumberE164(); + } + + public static function urlMailto(): string { + return 'mailto:' . self::email(); + } + + public static function iban(): string { + $account_number_lengths = [ + 'DE' => 10, + 'GB' => 10, + 'FR' => 11, + 'ES' => 12, + 'IT' => 12, + 'NL' => 10, + 'BE' => 9 + ]; + + $bank_code_lengths = [ + 'DE' => 8, + 'GB' => 6, + 'FR' => 5, + 'ES' => 4, + 'IT' => 5, + 'NL' => 4, + 'BE' => 3 + ]; + + $bank_codes = array_keys($bank_code_lengths); + $country_code = $bank_codes[array_rand($bank_codes)]; + $bank_code_length = $bank_code_lengths[$country_code]; + + $generateRandomNumericString = function($length) { + $number = ''; + for ($i = 0; $i < $length; $i++) { + $number .= mt_rand(0, 9); + } + return $number; + }; + + $bank_code = $generateRandomNumericString($bank_code_length); + $account_number_length = $account_number_lengths[$country_code]; + $account_number = $generateRandomNumericString($account_number_length); + + $iban_base = $country_code . '00' . $bank_code . $account_number; + + $iban_numeric = ''; + foreach (str_split($iban_base) as $char) { + if (ctype_alpha($char)) { + $iban_numeric .= ord($char) - 55; + } else { + $iban_numeric .= $char; + } + } + + $mod97 = bcmod($iban_numeric, '97'); + $check_digits = str_pad(98 - $mod97, 2, '0', STR_PAD_LEFT); + + return $country_code . $check_digits . $bank_code . $account_number; + } + + public static function ean13(): string { + $ean12 = ''; + for ($i = 0; $i < 12; $i++) { + $ean12 .= mt_rand(0, 9); + } + + $sum = 0; + for ($i = 0; $i < 12; $i++) { + $digit = (int)$ean12[$i]; + if ($i % 2 === 0) { + $sum += $digit; + } else { + $sum += $digit * 3; + } + } + + $check_digit = (10 - ($sum % 10)) % 10; + + return $ean12 . $check_digit; + } + + public static function username(): string { + $usernames = [ + 'user', 'coolUser', 'jane-doe', 'johnnyBravo', 'theRealMike', + 'superstar', 'gameMaster', 'techGuru', 'quickSilver', 'happyCamper', + 'blueSky', 'codingWizard', 'magicMikey', 'fastTrack', 'misterX', + 'adventureSeeker', 'pixelPioneer', 'ninja-warrior', 'starGazer', 'drSmart', + 'boldExplorer', 'zenMaster', 'risingStar', 'rocket-rider', 'digitalNomad', + 'echoEcho', 'nightOwl', 'lightSpeed', 'trueBeliever', 'cyberHawk', + 'galacticHero', 'luckyCharm', 'urbanVibes', 'silentStorm', 'wildWanderer', + 'moonWalker', 'brightStar', 'vividDreamer', 'vortexVoyager', 'infiniteLoop', + 'horizonChaser', 'quickSilverFox', 'shadowKnight', 'dataMaster', 'epicQuest', + 'cosmicDancer', 'virtualVictor', 'alphaBravo', 'gammaRay', 'quantumLeap', + 'alphaWolf', 'digitalDynamo', 'codeNinja', 'retro-rider', 'futureFreak', + 'hyperLink', 'wizardKing', 'neonNinja', 'techTitan', 'starshipPilot', + 'legendaryHero', 'phantomShadow', 'urbanLegend', 'novaStar', 'daringDiva', + 'trailBlazer', 'cyberChampion', 'epicGamer', 'stellarScribe', 'stormChaser', + 'lunarExplorer', 'plasma-bolt', 'infinityEdge', 'quantumQuest', 'stellar-voyager' + ]; + + $number = mt_rand(0, 9999); + + return $usernames[array_rand($usernames)] . $number; + } + + public static function firstname($lang = null): string { + $map_lang_firstnames = [ + 'en' => [ + 'John', 'Jane', 'Michael', 'Emily', 'Robert', 'Jessica', 'David', 'Sarah', + 'James', 'Laura', 'Daniel', 'Sophia', 'Matthew', 'Olivia', 'Andrew', 'Isabella', + 'William', 'Mia', 'Joseph', 'Charlotte', 'Charles', 'Amelia', 'Thomas', 'Harper', + 'Christopher', 'Evelyn', 'Benjamin', 'Abigail', 'Samuel', 'Ella', 'Henry', 'Avery', + 'Lucas', 'Sofia', 'Jack', 'Grace', 'Jackson', 'Chloe', 'Ethan', 'Zoe', 'Alexander', + 'Lily', 'Ryan', 'Hannah', 'Nathan', 'Scarlett', 'Gabriel', 'Aria', 'Carter', 'Mila', + 'Isaac', 'Ella', 'Luke', 'Madison', 'Owen', 'Penelope', 'Caleb', 'Riley', 'Aiden', + 'Samantha', 'Dylan', 'Eleanor', 'Joshua', 'Layla', 'Mason', 'Nora', 'Logan', 'Lila', + 'Eli', 'Hazel', 'Cameron', 'Audrey', 'Sebastian', 'Ellie', 'Grayson', 'Stella', + 'Julian', 'Luna', 'Hudson', 'Lila', 'Wyatt', 'Nina', 'Mateo', 'Cora', 'Isaiah', + 'Vivian', 'Jordan', 'Katherine', 'Leo', 'Mackenzie', 'Harrison', 'Paige', 'Evan', + 'Alice', 'Jaxon', 'Eliana', 'Asher', 'Lydia', 'Leo', 'Julia', 'Miles', 'Caroline', + 'Jeremiah', 'Kylie', 'Jasper', 'Adeline', 'Roman', 'Piper', 'Ezekiel', 'Claire', + 'Xavier', 'Riley', 'Sawyer', 'Serenity', 'Kinsley', 'Maya', 'Zachary', 'Madeline', + 'Ariana', 'Aiden', 'Eliza', 'Avery', 'Liam', 'Sophie', 'Jaxon', 'Evangeline', + 'Daniel', 'Anna', 'Hudson', 'Natalie', 'Eli', 'Mia', 'Sebastian', 'Quinn', + 'Jameson', 'Everly', 'Santiago', 'Aurora', 'Roman', 'Naomi', 'Jackson', 'Ivy', + 'Finn', 'Riley', 'Oliver', 'Jade', 'Landon', 'Brianna', 'Gavin', 'Savannah', + 'Connor', 'Lily', 'Parker', 'Aubrey', 'Nolan', 'Violet', 'Bentley', 'Clara', + 'Levi', 'Ruby', 'Carson', 'Alyssa', 'Hunter', 'Faith', 'Eli', 'Zoey', 'Adrian', + 'Sienna', 'Cooper', 'Elise', 'Brody', 'Genesis', 'Grant', 'Harley', 'Tristan', + 'Eva', 'Easton', 'Sage', 'Tanner', 'Summer', 'Dominic', 'Maddie', 'Micah', + 'Tessa', 'Elias', 'Brooke', 'Elliot', 'Mallory', 'Theo', 'Delilah', 'Ryder', + 'Lana', 'Beckett', 'Reese', 'Axel', 'Anastasia', 'Malachi', 'Gemma', 'Bennett', + 'Talia', 'Brayden', 'Nadia', 'Silas', 'Camila', 'Jonah', 'Iris', 'Maxwell', + 'Isla', 'Tyler', 'Jasmine', 'Diego', 'Nova', 'Eric', 'Maren', 'Dean', 'Bianca', + 'Lincoln', 'Paisley', 'Hayden', 'Rose', 'Declan', 'Carmen', 'Oscar', 'Willa', + 'Griffin', 'Aspen', 'Ronan', 'Freya', 'Ezra', 'Willow', 'Kaden', 'Georgia' + ], + 'fr' => [ + 'Jean', 'Marie', 'Pierre', 'Sophie', 'Louis', 'Camille', 'Paul', 'Juliette', + 'Jacques', 'Chloé', 'Léon', 'Clara', 'Henri', 'Lucie', 'Thomas', 'Élodie', + 'Philippe', 'Manon', 'Michel', 'Léa', 'Nicolas', 'Amandine', 'François', 'Anaïs', + 'Antoine', 'Aurélie', 'Guillaume', 'Margaux', 'Étienne', 'Charlotte', 'Benoît', + 'Alice', 'Maxime', 'Julie', 'Hugo', 'Emilie', 'Théo', 'Isabelle', 'Vincent', + 'Valérie', 'Laurent', 'Cécile', 'Olivier', 'Maëlys', 'Damien', 'Catherine', + 'Adrien', 'Amélie', 'Georges', 'Émilie', 'Baptiste', 'Inès', 'Rémi', 'Océane', + 'Mathieu', 'Florian', 'Yves', 'Elsa', 'René', 'Jade', 'Claude', 'Clémentine', + 'André', 'Victoria', 'Gérard', 'Laure', 'Lucas', 'Sarah', 'Alain', 'Gabrielle', + 'Patrick', 'Madeleine', 'Simon', 'Louise', 'Raphaël', 'Soline', 'Arnaud', 'Léna', + 'Sébastien', 'Victoire', 'Gaspard', 'Maëlle', 'Charles', 'Rose', 'Mathis', 'Fanny', + 'Luc', 'Noémie', 'Christophe', 'Caroline', 'David', 'Jeanne', 'Emmanuel', 'Justine', + 'Xavier', 'Adèle', 'Pascal', 'Diane', 'Romain', 'Noé', 'Marc', 'Marion', 'Gaël', + 'Coralie', 'Cédric', 'Ariane','Françoise', 'Yvonne', 'Clément', 'Solange', 'Mathilde', + 'Bérénice', 'Thierry', 'Agnès', 'Pascaline', 'Alix', 'Roland', 'Brigitte', 'Sylvain', + 'Estelle', 'Fabrice', 'Lilian', 'Josiane', 'Éric', 'Serge', 'Cyril', 'Bernadette', + 'Guilhem', 'Axelle', 'Dominique', 'Ludovic', 'Véronique', 'Raymond', 'Sandrine', + 'Patrice', 'Colette', 'Basile', 'Félix', 'Jean-Marc', 'Maurice', 'Sylvie', 'Jacqueline', + 'Augustin', 'Gaston', 'Jean-Baptiste', 'Odile', 'Arlette', 'Marius', 'Christiane', + 'Fabien', 'Louison', 'Léonie', 'Yann', 'Noémie', 'Raphaëlle', 'Sébastienne', 'Florence', + 'Lucien', 'Jean-Luc', 'Fernand', 'Antoinette', 'Gisèle', 'Solène', 'Angèle', 'Edmond', + 'Céleste', 'Hélène', 'Violette' + ] + ]; + + if(is_null($lang) || !isset($map_lang_firstnames[$lang])) { + $all_firstnames = array_merge( + $map_lang_firstnames['en'], + $map_lang_firstnames['fr'] + ); + + return $all_firstnames[array_rand($all_firstnames)]; + } + + return $map_lang_firstnames[$lang][array_rand($map_lang_firstnames[$lang])]; + } + + public static function lastname($lang = null): string { + $map_lang_lastnames = [ + 'en' => [ + 'Smith', 'Johnson', 'Williams', 'Jones', 'Brown', 'Davis', 'Miller', 'Wilson', + 'Moore', 'Taylor', 'Anderson', 'Thomas', 'Jackson', 'White', 'Harris', 'Martin', + 'Thompson', 'Garcia', 'Martinez', 'Robinson', 'Clark', 'Rodriguez', 'Lewis', + 'Lee', 'Walker', 'Hall', 'Allen', 'Young', 'King', 'Wright', 'Scott', 'Torres', + 'Nguyen', 'Hill', 'Adams', 'Baker', 'Nelson', 'Carter', 'Mitchell', 'Perez', + 'Roberts', 'Turner', 'Phillips', 'Campbell', 'Parker', 'Evans', 'Edwards', + 'Collins', 'Stewart', 'Sanchez', 'Morris', 'Rogers', 'Reed', 'Cook', 'Morgan', + 'Bell', 'Murphy', 'Bailey', 'Rivera', 'Cooper', 'Richardson', 'Cox', 'Howard', + 'Ward', 'Flores', 'Wood', 'James', 'Bennett', 'Gray', 'Mendoza', 'Cruz', + 'Hughes', 'Price', 'Myers', 'Long', 'Foster', 'Sanders', 'Ross', 'Morales', + 'Powell', 'Jenkins', 'Perry', 'Butler', 'Barnes', 'Fisher', 'Henderson', + 'Coleman', 'Simmons', 'Patterson', 'Jordan', 'Reynolds', 'Hamilton', + 'Graham', 'Kim', 'Gonzalez', 'Vasquez', 'Sullivan', 'Bryant', 'Alexander', + 'Russell', 'Griffin', 'Diaz', 'Hayes', 'Wells', 'Chavez', 'Burke', 'Wood', + 'Harrison', 'Gordon', 'Walters', 'McDonald', 'Murray', 'Ford', 'Hamilton', + 'Gibson', 'Ellis', 'Ramos', 'Fisher', 'George', 'Miller', 'Harris', 'James', + 'Stone', 'Richards', 'Hunter', 'Bennett', 'Perry', 'Matthews', 'Hughes', + 'Palmer', 'Burns', 'Floyd', 'Nguyen', 'Snyder', 'Bishop', 'Newman', 'Boone', + 'Dean', 'Carr', 'Cunningham', 'Sampson', 'Marshall', 'Barnett', 'Farrell', + 'Weaver', 'Wade', 'Bradley', 'Mason', 'Newton', 'Olson', + 'Hawkins', 'Chapman', 'Bowman', 'Lawrence', 'Glover', 'Barber', 'Grant', 'Wallace', + 'Keller', 'Webb', 'Spencer', 'Harvey', 'Brooks', 'Pearson', 'Francis', 'Burgess', + 'Graves', 'Lambert', 'Cross', 'Tucker', 'Fields', 'Reeves', 'Gibbs', 'Porter', + 'Daniels', 'Brady', 'Owen', 'Horton', 'McCarthy', 'Fletcher', 'Simon', 'Norris', + 'Clayton', 'Kemp', 'Fuller', 'Tyler', 'Pearce', 'Moss', 'Rowe', 'Hodges', 'Barker', + 'Hardy', 'Jennings', 'Gilbert', 'Payne', 'Webster', 'Neal', 'Sutton', 'Davidson', + 'Carlson', 'Morton', 'Kirk', 'Holland', 'Greer', 'Wheeler', 'Peters', 'Fordham' + ], + 'fr' => [ + 'Martin', 'Bernard', 'Dubois', 'Thomas', 'Robert', 'Richard', 'Petit', 'Durand', 'Leroy', 'Moreau', + 'Simon', 'Laurent', 'Lefebvre', 'Michel', 'Garcia', 'David', 'Bertrand', 'Roux', 'Vincent', 'Fournier', + 'Morel', 'Girard', 'Andre', 'Lefevre', 'Mercier', 'Dupont', 'Lambert', 'Bonnet', 'Francois', 'Martinez', + 'Legrand', 'Garnier', 'Faure', 'Rousseau', 'Blanc', 'Guerin', 'Muller', 'Henry', 'Roussel', 'Nicolas', + 'Perrin', 'Morin', 'Mathieu', 'Clement', 'Gauthier', 'Dumont', 'Lopez', 'Fontaine', 'Chevalier', + 'Robin', 'Masson', 'Sanchez', 'Gerard', 'Nguyen', 'Boyer', 'Denis', 'Lemoine', 'Duval', 'Joly', + 'Gautier', 'Roger', 'Renaud', 'Gaillard', 'Hamond', 'Boucher', 'Carpentier', 'Menard', 'Marechal', + 'Charpentier', 'Dupuis', 'Leclerc', 'Poirier', 'Guillaume', 'Leconte', 'Benoit', 'Collet', + 'Perrot', 'Jacquet', 'Rey', 'Gilles', 'Herve', 'Charrier', 'Schmitt', 'Baron', 'Perret', + 'Leblanc', 'Verdier', 'Giraud', 'Marty', 'Lemoine', 'Poulain', 'Vallet', 'Renard', + 'Marion', 'Marchand', 'Chauvin', 'Langlois', 'Teixeira', 'Bellamy', 'Lemoigne', 'Bazin', + 'Da Silva', 'Delorme', 'Aubry', 'Ferreira', 'Chauvet', 'Delaunay', 'Joubert', 'Vidal', + 'Pires', 'Blondel', 'Noel', 'Collin', 'Lucas', 'Monnier', 'Breton', 'Lejeune', 'Prevost', + 'Allard', 'Pichon', 'Le Gall', 'Lavigne', 'Roy', 'Gros', 'Chartier', 'Briand', 'Maillet', + 'Lemois', 'Dufour', 'Boutin', 'Guichard', 'Vasseur', 'Hoarau', 'Lebrun', 'Giraudet', + 'Dubois', 'Maillard', 'Millet', 'Carre', 'Coste', 'Laborde', 'Bertin', 'Moulin', + 'Turpin', 'Deschamps', 'Barthelemy', 'Descamps', 'Riviere', 'Guilbert', 'Tanguy', + 'Duchamp', 'Pasquier', 'Gaudin', 'Vial', 'Letellier', 'Meunier', 'Bouchet', 'Hebert', + 'Gosselin', 'Le Roux', 'Renou', 'Guillon', 'Delattre', 'Lefranc', 'Peltier', 'Delacroix', + 'Labbe', 'Bellanger', 'Perrier', 'Chretien', 'Bouvet', 'Ferrand', 'Vallee', 'Boulanger', + 'Vautier', 'Morvan', 'Leclercq', 'Picard', 'Jourdain', 'Cornu', 'Bodin', 'Courtois', + 'Duhamel', 'Leveque', 'Leconte', 'Aubert', 'Delaire', 'Letourneau', 'Tessier', 'Barre', + 'Fleury', 'Mallet', 'Deniau', 'Royer', 'Rigal', 'Levy', 'Bouchard', 'Charron', 'Laroche' + ] + ]; + + if(is_null($lang) || !isset($map_lang_lastnames[$lang])) { + $all_lastnames = array_merge( + $map_lang_lastnames['en'], + $map_lang_lastnames['fr'] + ); + + return $all_lastnames[array_rand($all_lastnames)]; + } + + return $map_lang_lastnames[$lang][array_rand($map_lang_lastnames[$lang])]; + } + + public static function fullname($lang = null): string { + return sprintf('%s %s', self::firstname($lang), self::lastname($lang)); + } + + public static function legalName($lang = null): string { + $map_lang_legalNames = [ + 'en' => [ + 'Pinnacle Innovations Ltd.', 'GlobalTech Solutions LLC', + 'Blue Horizon Enterprises Inc.', 'Silverline Consulting Group', + 'Evergreen Holdings Co.', 'Brightway Capital Partners', + 'NextGen Software Corp.', 'Summit Legal Advisors LLP', + 'Aspire Marketing Associates', 'Crestview Financial Services', + 'Vanguard Media Networks', 'Oceanic Development Group', + 'Phoenix Engineering Systems', 'Cascade Resource Management', + 'Momentum Logistics Group', 'EagleRock Ventures LLC', + 'Fusion Health & Wellness Inc.', 'Broadway Research Institute', + 'Skyline Construction Co.', 'Peak Performance Consulting', + 'Heritage Legal Solutions LLP', 'Infinity Investment Trust', + 'Starlight Communications Inc.', 'Aurora Environmental Services', + 'Velocity Tech Innovators LLC', 'Redwood Analytics Solutions', + 'Everest Property Management', 'Sapphire Insurance Group Ltd.', + 'Nova Digital Media Inc.', 'Paramount Energy Solutions', + 'UrbanCore Construction Co.', 'TerraNova Agricultural Services', + 'Quantum Financial Consulting', 'Synergy Global Partners', + 'Horizon Security Solutions LLC', 'NorthStar Investments Group', + 'Zenith Technology Ventures', 'PrimeCare Medical Solutions', + 'Aurora Legal Advisory Group', 'Lakeside Manufacturing Co.', + 'Falcon Ridge Asset Management', 'Emerald Coast Logistics LLC', + 'Atlas Legal Services LLP', 'Titanium Energy Systems', + 'Genesis Marketing Innovations', 'Renaissance Capital Advisors', + 'TrueNorth Consulting Group', 'BluePeak Industries Inc.', + 'Omega Transportation Services', 'GoldenBridge Infrastructure Co.', + 'Sunrise Financial Holdings', 'Pioneer Technology Group LLC' + ], + 'fr' => [ + 'Solutions Pinnacle SARL', 'GlobalTech Innovations SAS', + 'Horizon Bleu Entreprises SA', 'Consulting Silverline Group', + 'Evergreen Holdings SARL', 'Capital Brightway Partners', + 'NextGen Software Corp.', 'Conseil Juridique Summit SCP', + 'Aspire Marketing Associés', 'Services Financiers Crestview', + 'Vanguard Médias Réseaux SARL', 'Groupe de Développement Océanique', + 'Phoenix Ingénierie Systèmes', 'Gestion des Ressources Cascade', + 'Momentum Logistique SARL', 'EagleRock Ventures SAS', + 'Fusion Santé & Bien-être Inc.', 'Institut de Recherche Broadway', + 'Construction Skyline SARL', 'Performance Consulting Peak', + 'Solutions Juridiques Héritage SCP', 'Infinity Investissement Trust', + 'Communications Starlight Inc.', 'Services Environnementaux Aurora', + 'Innovateurs Technologiques Velocity SAS', 'Alpha Conseil et Stratégie', + 'Groupe Nexus Immobilier', 'Aquitaine Développement Durable', + 'Astéria Informatique SARL', 'Helios Technologies SAS', + 'Artisanat Moderne & Tradition', 'Biotech Avancée France SA', + 'Espace Vert Solutions', 'Union des Commerçants Réunis', + 'Nouvelle Génération Énergies', 'Réseaux Mondial Média', + 'EcoSys Environnement SAS', 'Orion Énergie Renouvelable', + 'Alliance Pharmaceutique France', 'Cœur de Ville Projets Urbains', + 'Pôle d\'Innovation Numérique', 'Ciel Bleu Aviation SARL', + 'Altura Services Financiers', 'Vision Logistique Internationale', + 'Verdi Éco-Construction SARL', 'Genèse Création et Design', + 'Solstice Groupe Juridique', 'TechnoVentures France', + 'Esprit Nature & Bien-être SARL', 'Voies Nouvelles Mobilité SAS', + 'Groupe Horizon Santé SAS' + ] + ]; + + if(is_null($lang) || !isset($map_lang_legalNames[$lang])) { + $all_legal_names = array_merge( + $map_lang_legalNames['en'], + $map_lang_legalNames['fr'] + ); + + return $all_legal_names[array_rand($all_legal_names)]; + } + + return $map_lang_legalNames[$lang][array_rand($map_lang_legalNames[$lang])]; + } + + public static function addressStreet($lang = null): string { + $number = mt_rand(1, 1200); + + $map_lang_streets = [ + 'en' => [ + 'High Street', 'Main Road', 'Church Lane', 'King Street', 'Victoria Avenue', 'Queen’s Road', + 'Park Lane', 'Elm Street', 'Bridge Road', 'Cedar Drive', 'Oxford Street', 'Market Square', + 'Mill Lane', 'Greenwich Way', 'Meadow Road', 'Kingfisher Close', 'Rosemary Avenue', + 'West End', 'Holly Road', 'Sunset Boulevard', 'Newcastle Street', 'Broadway', 'Pine Hill', + 'St. James’s Street', 'Bayview Road', 'Shakespeare Avenue', 'Cloverleaf Drive', 'Springfield Road', + 'Wellington Street', 'Harrison Close', 'Golden Grove', 'Elmwood Avenue', 'Rose Lane', + 'Abbey Road', 'Silver Street', 'Orchard Way', 'Granite Road', 'Oakfield Drive', 'Riverside Walk', + 'Broad Street', 'Main Avenue', 'River Lane', 'Hampton Road', 'Baker Street', 'Long Lane', + 'Park Avenue', 'Crescent Road', 'Hilltop Drive', 'Northgate Street', 'Cleveland Avenue', + 'St. John’s Road', 'Meadowbrook Lane', 'Silverwood Drive', 'Greenfield Road', 'Windsor Crescent', + 'Castle Street', 'Fairview Avenue', 'Ridgeway Road', 'Cumberland Street', 'Abbey Lane', + 'Kingsway', 'Dove Court', 'Hollingsworth Road', 'Starlight Avenue', 'Sunrise Lane', + 'Copperfield Road', 'Thornfield Drive', 'Holly Hill', 'Fairfax Street', 'Violet Lane', + 'Laurel Avenue', 'Tudor Street', 'Bramble Road', 'Claremont Drive', 'Woodland Avenue', + 'Glenwood Road', 'Beaumont Street', 'Ridgewood Avenue', 'Daisy Lane', 'Waverley Road', + 'King’s Crescent', 'Hawthorn Road', 'Elmwood Drive', 'Haven Street', 'Meadow Street' + ], + 'fr' => [ + 'Rue de la Paix', 'Avenue des Champs-Élysées', 'Boulevard Saint-Germain', 'Place de la Concorde', + 'Rue de Rivoli', 'Avenue Victor Hugo', 'Boulevard Haussmann', 'Rue du Faubourg Saint-Honoré', + 'Avenue de l\'Opéra', 'Rue Saint-Denis', 'Place Vendôme', 'Rue de la République', + 'Boulevard Voltaire', 'Rue Lafayette', 'Avenue de la Grande Armée', 'Rue du Bac', + 'Avenue Montaigne', 'Boulevard de Sébastopol', 'Place d’Italie', 'Rue de la Fontaine', + 'Rue des Rosiers', 'Rue de Vaugirard', 'Avenue de Paris', 'Boulevard de Strasbourg', + 'Place des Vosges', 'Rue Monge', 'Avenue Foch', 'Rue de la Garenne', 'Boulevard Saint-Michel', + 'Rue de la Chapelle', 'Place du Trocadéro', 'Rue de l’Université', 'Boulevard Diderot', + 'Rue des Écoles', 'Avenue de l\'Alma', 'Rue du Temple', 'Boulevard de la Madeleine', + 'Rue du Commerce', 'Avenue des Ternes', 'Boulevard de la Liberté', 'Place des Jardins', + 'Rue du Pont-Neuf', 'Avenue de la République', 'Boulevard de la Gare', 'Rue des Fleurs', + 'Place du Marché', 'Rue du Général Leclerc', 'Avenue des Écoles', 'Boulevard du Palais', + 'Rue des Lilas', 'Avenue de la Mairie', 'Boulevard des Capucines', 'Place des Armes', + 'Rue des Peupliers', 'Avenue du Président Wilson', 'Boulevard de l’Indépendance', 'Rue des Roses', + 'Place du Général de Gaulle', 'Rue des Pommiers', 'Avenue de la Victoire', 'Boulevard de la Mer', + 'Rue des Vergers', 'Avenue des Arts', 'Boulevard de la Côte', 'Place du Château', 'Rue de la Gare', + 'Rue de l’Église', 'Avenue de la Forêt', 'Boulevard du Parc', 'Place du Soleil', 'Rue des Vignes', + 'Rue de la Liberté', 'Avenue des Érables', 'Boulevard des Temples', 'Place de la République', + 'Rue des Champs', 'Avenue des Pyramides', 'Boulevard des Pruniers', 'Place de la Bastille' + ] + ]; + + if(is_null($lang) || !isset($map_lang_streets[$lang])) { + $all_streets = array_merge( + $map_lang_streets['en'], + $map_lang_streets['fr'] + ); + + return $all_streets[array_rand($all_streets)] . ' ' . $number; + } + + return $map_lang_streets[$lang][array_rand($map_lang_streets[$lang])] . ' ' . $number; + } + + public static function addressZip(): string { + return mt_rand(1000, 9999); + } + + public static function addressCity($lang = null): string { + $map_lang_cities = [ + 'en' => [ + 'New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix', + 'Philadelphia', 'San Antonio', 'San Diego', 'Dallas', 'San Jose', + 'Toronto', 'Vancouver', 'Montreal', 'Calgary', 'Edmonton', + 'Ottawa', 'Winnipeg', 'Quebec City', 'Hamilton', 'Kitchener', + 'London', 'Birmingham', 'Manchester', 'Glasgow', 'Liverpool', + 'Edinburgh', 'Leeds', 'Sheffield', 'Bristol', 'Cardiff', + 'Sydney', 'Melbourne', 'Brisbane', 'Perth', 'Adelaide', + 'Gold Coast', 'Canberra', 'Hobart', 'Darwin', 'Newcastle', + 'Berlin', 'Hamburg', 'Munich', 'Cologne', 'Frankfurt', + 'Stuttgart', 'Dusseldorf', 'Dortmund', 'Essen', 'Leipzig', + 'Paris', 'Marseille', 'Lyon', 'Toulouse', 'Nice', + 'Nantes', 'Montpellier', 'Strasbourg', 'Bordeaux', 'Lille', + 'Rome', 'Milan', 'Naples', 'Turin', 'Palermo', + 'Genoa', 'Bologna', 'Florence', 'Catania', 'Venice', + 'Madrid', 'Barcelona', 'Valencia', 'Seville', 'Zaragoza', + 'Malaga', 'Murcia', 'Palma', 'Las Palmas', 'Bilbao', + 'Brussels', 'Antwerp', 'Ghent', 'Bruges', 'Liege', + 'Namur', 'Ostend', 'Leuven', 'Hasselt', 'Mechelen', + 'Amsterdam', 'Rotterdam', 'The Hague', 'Utrecht', 'Eindhoven', + 'Groningen', 'Maastricht', 'Arnhem', 'Nijmegen', 'Haarlem', + 'Zurich', 'Geneva', 'Bern', 'Basel', 'Lausanne', + 'Lucerne', 'St. Moritz', 'Zug', 'Neuchatel', 'La Chaux-de-Fonds', + 'Tokyo', 'Osaka', 'Kyoto', 'Nagoya', 'Hiroshima', + 'Fukuoka', 'Kobe', 'Yokohama', 'Sapporo', 'Sendai', + 'Beijing', 'Shanghai', 'Guangzhou', 'Shenzhen', 'Chengdu', + 'Hong Kong', 'Hangzhou', 'Nanjing', 'Wuhan', 'Xi\'an', + 'Mumbai', 'Delhi', 'Bangalore', 'Hyderabad', 'Ahmedabad', + 'Chennai', 'Kolkata', 'Pune', 'Jaipur', 'Surat', + 'Sao Paulo', 'Rio de Janeiro', 'Salvador', 'Fortaleza', 'Belo Horizonte', + 'Brasilia', 'Curitiba', 'Manaus', 'Recife', 'Porto Alegre', + 'Johannesburg', 'Cape Town', 'Durban', 'Pretoria', 'Port Elizabeth', + 'Bloemfontein', 'East London', 'Polokwane', 'Nelspruit', 'Mbombela' + ], + 'fr' => [ + 'New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix', + 'Philadelphie', 'San Antonio', 'San Diego', 'Dallas', 'San José', + 'Toronto', 'Vancouver', 'Montréal', 'Calgary', 'Edmonton', + 'Ottawa', 'Winnipeg', 'Québec', 'Hamilton', 'Kitchener', + 'Londres', 'Birmingham', 'Manchester', 'Glasgow', 'Liverpool', + 'Édimbourg', 'Leeds', 'Sheffield', 'Bristol', 'Cardiff', + 'Sydney', 'Melbourne', 'Brisbane', 'Perth', 'Adélaïde', + 'Gold Coast', 'Canberra', 'Hobart', 'Darwin', 'Newcastle', + 'Berlin', 'Hambourg', 'Munich', 'Cologne', 'Francfort', + 'Stuttgart', 'Dusseldorf', 'Dortmund', 'Essen', 'Leipzig', + 'Paris', 'Marseille', 'Lyon', 'Toulouse', 'Nice', + 'Nantes', 'Montpellier', 'Strasbourg', 'Bordeaux', 'Lille', + 'Rome', 'Milan', 'Naples', 'Turin', 'Palermo', + 'Gênes', 'Bologne', 'Florence', 'Catane', 'Venise', + 'Madrid', 'Barcelone', 'Valence', 'Séville', 'Saragosse', + 'Malaga', 'Murcie', 'Palma', 'Las Palmas', 'Bilbao', + 'Bruxelles', 'Anvers', 'Gand', 'Bruges', 'Liège', + 'Namur', 'Ostende', 'Leuven', 'Hasselt', 'Malines', + 'Amsterdam', 'Rotterdam', 'La Haye', 'Utrecht', 'Eindhoven', + 'Groningue', 'Maastricht', 'Arnhem', 'Nijmegen', 'Haarlem', + 'Zurich', 'Genève', 'Berne', 'Bâle', 'Lausanne', + 'Lucerne', 'Saint-Moritz', 'Zoug', 'Neuchâtel', 'La Chaux-de-Fonds', + 'Tokyo', 'Osaka', 'Kyoto', 'Nagoya', 'Hiroshima', + 'Fukuoka', 'Kobe', 'Yokohama', 'Sapporo', 'Sendai', + 'Pékin', 'Shanghai', 'Guangzhou', 'Shenzhen', 'Chengdu', + 'Hong Kong', 'Hangzhou', 'Nankin', 'Wuhan', 'Xi’an', + 'Mumbai', 'Delhi', 'Bangalore', 'Hyderabad', 'Ahmedabad', + 'Chennai', 'Kolkata', 'Pune', 'Jaipur', 'Surat', + 'Sao Paulo', 'Rio de Janeiro', 'Salvador', 'Fortaleza', 'Belo Horizonte', + 'Brasilia', 'Curitiba', 'Manaus', 'Recife', 'Porto Alegre', + 'Johannesbourg', 'Le Cap', 'Durban', 'Prétoria', 'Port Elizabeth', + 'Bloemfontein', 'East London', 'Polokwane', 'Nelspruit', 'Mbombela' + ] + ]; + + if(is_null($lang) || !isset($map_lang_cities[$lang])) { + $all_cities = array_merge( + $map_lang_cities['en'], + $map_lang_cities['fr'] + ); + + return $all_cities[array_rand($all_cities)]; + } + + return $map_lang_cities[$lang][array_rand($map_lang_cities[$lang])]; + } + + public static function addressCountry($lang = null): string { + $map_lang_countries = [ + 'en' => [ + 'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', + 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', + 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', + 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', + 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', + 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cabo Verde', 'Cambodia', + 'Cameroon', 'Canada', 'Central African Republic', 'Chad', 'Chile', + 'China', 'Colombia', 'Comoros', 'Congo, Democratic Republic of the', 'Congo, Republic of the', + 'Costa Rica', 'Croatia', 'Cuba', 'Cyprus', 'Czech Republic', + 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', + 'East Timor', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', + 'Eritrea', 'Estonia', 'Eswatini', 'Ethiopia', 'Fiji', + 'Finland', 'France', 'Gabon', 'Gambia', 'Georgia', + 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', + 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Honduras', + 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', + 'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica', + 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', + 'Korea, North', 'Korea, South', 'Kosovo', 'Kuwait', 'Kyrgyzstan', + 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', + 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Madagascar', + 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', + 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', + 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', + 'Mozambique', 'Myanmar', 'Namibia', 'Nauru', 'Nepal', + 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', + 'North Macedonia', 'Norway', 'Oman', 'Pakistan', 'Palau', + 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', + 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', + 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines', 'Samoa', + 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', + 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', + 'Solomon Islands', 'Somalia', 'South Africa', 'South Sudan', 'Spain', + 'Sri Lanka', 'Sudan', 'Suriname', 'Sweden', 'Switzerland', + 'Syria', 'Taiwan', 'Tajikistan', 'Tanzania', 'Thailand', + 'Timor-Leste', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', + 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', + 'United Arab Emirates', 'United Kingdom', 'United States', 'Uruguay', 'Uzbekistan', + 'Vanuatu', 'Vatican City', 'Venezuela', 'Vietnam', 'Yemen', + 'Zambia', 'Zimbabwe' + ], + 'fr' => [ + 'Afghanistan', 'Albanie', 'Algérie', 'Andorre', 'Angola', + 'Antigua-et-Barbuda', 'Argentine', 'Arménie', 'Australie', 'Autriche', + 'Azerbaïdjan', 'Bahamas', 'Bahreïn', 'Bangladesh', 'Barbade', + 'Bélarus', 'Belgique', 'Belize', 'Bénin', 'Bhoutan', + 'Bolivie', 'Bosnie-Herzégovine', 'Botswana', 'Brésil', 'Brunei', + 'Bulgarie', 'Burkina Faso', 'Burundi', 'Cap-Vert', 'Cambodge', + 'Cameroun', 'Canada', 'République Centrafricaine', 'Tchad', 'Chili', + 'Chine', 'Colombie', 'Comores', 'République Démocratique du Congo', 'République du Congo', + 'Côte d\'Ivoire', 'Costa Rica', 'Croatie', 'Cuba', 'Chypre', + 'République tchèque', 'Danemark', 'Djibouti', 'Dominique', 'République Dominicaine', + 'Timor oriental', 'Équateur', 'Égypte', 'El Salvador', 'Guinée équatoriale', + 'Érythrée', 'Estonie', 'Eswatini', 'Éthiopie', 'Fidji', + 'Finlande', 'France', 'Gabon', 'Gambie', 'Géorgie', + 'Allemagne', 'Ghana', 'Grèce', 'Grenade', 'Guatemala', + 'Guinée', 'Guinée-Bissau', 'Guyane', 'Haïti', 'Honduras', + 'Hongrie', 'Islande', 'Inde', 'Indonésie', 'Iran', + 'Irak', 'Irlande', 'Israël', 'Italie', 'Jamaïque', + 'Japon', 'Jordanie', 'Kazakhstan', 'Kenya', 'Kiribati', + 'Corée du Nord', 'Corée du Sud', 'Kosovo', 'Koweït', 'Kyrgyzstan', + 'Laos', 'Lettonie', 'Liban', 'Lesotho', 'Liberia', + 'Libye', 'Liechtenstein', 'Lituanie', 'Luxembourg', 'Madagascar', + 'Malawi', 'Malaisie', 'Maldives', 'Mali', 'Malte', + 'Îles Marshall', 'Mauritanie', 'Maurice', 'Mexique', 'Micronésie', + 'Moldova', 'Monaco', 'Mongolie', 'Monténégro', 'Maroc', + 'Mozambique', 'Myanmar', 'Namibie', 'Nauru', 'Népal', + 'Pays-Bas', 'Nouvelle-Zélande', 'Nicaragua', 'Niger', 'Nigeria', + 'Macédoine du Nord', 'Norvège', 'Oman', 'Pakistan', 'Palaos', + 'Panama', 'Papouasie-Nouvelle-Guinée', 'Paraguay', 'Pérou', 'Philippines', + 'Pologne', 'Portugal', 'Qatar', 'Roumanie', 'Russie', + 'Rwanda', 'Saint-Kitts-et-Nevis', 'Sainte-Lucie', 'Saint-Vincent-et-les-Grenadines', 'Samoa', + 'Saint-Marin', 'Sao Tomé-et-Principe', 'Arabie Saoudite', 'Sénégal', 'Serbie', + 'Seychelles', 'Sierra Leone', 'Singapour', 'Slovaquie', 'Slovénie', + 'Îles Salomon', 'Somalie', 'Afrique du Sud', 'Soudan du Sud', 'Espagne', + 'Sri Lanka', 'Soudan', 'Suriname', 'Suède', 'Suisse', + 'Syrie', 'Taïwan', 'Tadjikistan', 'Tanzanie', 'Thaïlande', + 'Timor-Leste', 'Togo', 'Tonga', 'Trinité-et-Tobago', 'Tunisie', + 'Turquie', 'Turkménistan', 'Tuvalu', 'Ouganda', 'Ukraine', + 'Émirats Arabes Unis', 'Royaume-Uni', 'États-Unis', 'Uruguay', 'Ouzbékistan', + 'Vanuatu', 'Vatican', 'Venezuela', 'Vietnam', 'Yémen', + 'Zambie', 'Zimbabwe' + ] + ]; + + if(is_null($lang) || !isset($map_lang_countries[$lang])) { + $all_countries = array_merge( + $map_lang_countries['en'], + $map_lang_countries['fr'] + ); + + return $all_countries[array_rand($all_countries)]; + } + + return $map_lang_countries[$lang][array_rand($map_lang_countries[$lang])]; + } + + public static function address($lang = null): string { + return sprintf( + '%s, %s %s, %s', + self::addressStreet($lang), + self::addressZip(), + self::addressCity($lang), + self::addressCountry($lang) + ); + } + +} diff --git a/lib/equal/data/adapt/adapters/json/DataAdapterJsonBinary.class.php b/lib/equal/data/adapt/adapters/json/DataAdapterJsonBinary.class.php index 37899a6b0..a4d5b0dc5 100644 --- a/lib/equal/data/adapt/adapters/json/DataAdapterJsonBinary.class.php +++ b/lib/equal/data/adapt/adapters/json/DataAdapterJsonBinary.class.php @@ -55,7 +55,7 @@ public function adaptIn($value, $usage, $locale='en') { throw new \Exception(serialize(["not_valid_base64" => "Invalid base64 data URI value."]), QN_ERROR_INVALID_PARAM); } if(strlen($result) > constant('UPLOAD_MAX_FILE_SIZE')) { - throw new \Exception("file_exceeds_maximum_size", QN_ERROR_NOT_ALLOWED); + throw new \Exception("file_exceeds_maximum_size", QN_ERROR_INVALID_PARAM); } } else { diff --git a/lib/equal/data/adapt/adapters/json/DataAdapterJsonTime.class.php b/lib/equal/data/adapt/adapters/json/DataAdapterJsonTime.class.php index 9ef38bb6e..03d8d69a0 100644 --- a/lib/equal/data/adapt/adapters/json/DataAdapterJsonTime.class.php +++ b/lib/equal/data/adapt/adapters/json/DataAdapterJsonTime.class.php @@ -71,7 +71,8 @@ public function adaptOut($value, $usage, $locale='en') { } $hours = floor($value / 3600); $minutes = floor(($value % 3600) / 60); - return sprintf("%02d:%02d", $hours, $minutes); + $seconds = $value % (60); + return sprintf("%02d:%02d:%02d", $hours, $minutes, $seconds); } } diff --git a/lib/equal/orm/Domain.class.php b/lib/equal/orm/Domain.class.php index 45caf479c..43fcacd15 100644 --- a/lib/equal/orm/Domain.class.php +++ b/lib/equal/orm/Domain.class.php @@ -122,7 +122,7 @@ public function parse($object = [], $user = []) { // handle object references as `value` part if(is_string($value) && strpos($value, 'object.') == 0 ) { - $target = substr($value, 0, strlen('object.')); + $target = substr($value, strlen('object.')); if(!$object || !isset($object[$target])) { continue; } @@ -145,7 +145,7 @@ public function parse($object = [], $user = []) { } // handle user references as `value` part elseif(is_string($value) && strpos($value, 'user.') == 0) { - $target = substr($value, 0, strlen('user.')); + $target = substr($value, strlen('user.')); if(!$user || !isset($user[$target])) { continue; } @@ -245,8 +245,8 @@ public function evaluate($object) { /* * Domain checks and operations. - * a domain should always be composed of a serie of clauses against which a OR test is made - * a clause should always be composed of a serie of conditions agaisnt which a AND test is made + * a domain should always be composed of a series of clauses against which a OR test is made + * a clause should always be composed of a series of conditions against which a AND test is made * a condition should always be composed of a property operand, an operator, and a value */ diff --git a/lib/equal/orm/Model.class.php b/lib/equal/orm/Model.class.php index d122085b4..31ea98805 100644 --- a/lib/equal/orm/Model.class.php +++ b/lib/equal/orm/Model.class.php @@ -13,10 +13,10 @@ * This class holds the description of an object along with the values of the currently loaded/assigned fields. * * List of static methods for building new Collection objects (accessed through magic methods): - * @method \equal\orm\Collection id($id) - * @method \equal\orm\Collection ids(array $ids=[]) - * @method \equal\orm\Collection search(array $domain=[], array $params=[], $lang=null) - * @method \equal\orm\Collection create(array $values=null, $lang=null) + * @method static \equal\orm\Collection id($id) + * @method static \equal\orm\Collection static ids(array $ids=[]) + * @method static \equal\orm\Collection static search(array $domain=[], array $params=[], $lang=null) + * @method static \equal\orm\Collection static create(array $values=null, $lang=null) * * List of static methods with variable parameters: * (These methods are handled through __callStatic method to prevent PHP strict errors & aot warnings.) @@ -143,7 +143,7 @@ private function setDefaults($values=[]) { $this->values[$field] = $defaults[$field](); } else { - // do not call since there is an ambiguity (e.g. 'time') + // do not call since there is an ambiguity with scalar (e.g. 'time') $this->values[$field] = $defaults[$field]; } } @@ -210,7 +210,7 @@ public function offsetGet($field) { * Iterator methods */ - public function rewind() : void { + public function rewind(): void { reset($this->values); } @@ -218,15 +218,15 @@ public function current() { return current($this->values); } - public function key() : string { + public function key(): string { return key($this->values); } - public function next() : void { + public function next(): void { next($this->values); } - public function valid() : bool { + public function valid(): bool { $key = key($this->values); $res = ($key !== null && $key !== false); return $res; diff --git a/lib/equal/orm/ObjectManager.class.php b/lib/equal/orm/ObjectManager.class.php index 246f52d4c..cce897687 100644 --- a/lib/equal/orm/ObjectManager.class.php +++ b/lib/equal/orm/ObjectManager.class.php @@ -686,6 +686,7 @@ private function load($class, $ids, $fields, $lang) { } if(count($missing_ids)) { if(isset($schema[$field]['function'])) { + trigger_error("ORM::computing 'function' for '$field' of class '$class'", QN_REPORT_INFO); $res = $this->callonce($class, $schema[$field]['function'], $missing_ids, [], $lang); if($res > 0) { foreach($missing_ids as $oid) { @@ -701,6 +702,7 @@ private function load($class, $ids, $fields, $lang) { } elseif(isset($schema[$field]['relation']) && is_array($schema[$field]['relation'])) { try { + trigger_error("ORM::computing 'relation' for '$field' of class '$class'", QN_REPORT_INFO); $res = $class::ids($ids)->read($schema[$field]['relation'])->get(true); foreach($res as $elem) { $id = $elem['id']; @@ -708,12 +710,12 @@ private function load($class, $ids, $fields, $lang) { while(is_array($relation)) { $target = array_key_first($relation); if(is_numeric($target)) { - $om->cache[$table_name][$id][$lang][$field] = $elem[$relation[$target]]; + $om->cache[$table_name][$id][$lang][$field] = $elem[$relation[$target]] ?? null; break; } else { - $elem = $elem[$target]; - $relation = $relation[$target]; + $elem = $elem[$target] ?? null; + $relation = isset($relation[$target]) ? (array) $relation[$target] : null; } } } @@ -1264,8 +1266,8 @@ public function getModel($class) { $model = $this->getStaticInstance($class); } catch(Exception $e) { - trigger_error($e->getMessage(), QN_REPORT_ERROR); - // #memo - another autoload handler might be registered, so no exception can be raised + // #memo - another autoload handler might be registered, so no exception must be raised here + trigger_error('ORM::'.$e->getMessage(), QN_REPORT_ERROR); } return $model; } diff --git a/lib/equal/orm/usages/Usage.class.php b/lib/equal/orm/usages/Usage.class.php index adda6606b..59a13a8e9 100644 --- a/lib/equal/orm/usages/Usage.class.php +++ b/lib/equal/orm/usages/Usage.class.php @@ -83,6 +83,10 @@ public function getConstraints(): array { return []; } + public function generateRandomValue() { + return null; + } + final public function getName() : string { return $this->usage_str; } @@ -170,7 +174,7 @@ public function getScale(): int { public function __construct(string $usage_str) { // check usage string consistency - if(!preg_match('/([a-z]+)(\[([0-9]+)\])?\/?([-a-z0-9]*)(\.([-a-z0-9.]*))?(:(([-0-9a-z]*)\.?([0-9]*)))?({([0-9]+)(,([0-9]+))?})?/', $usage_str, $matches)) { + if(!preg_match('/([a-z]+)(\[([0-9]+)\])?\/?([-a-z0-9]*)(\.([-a-z0-9.]*))?(:(([-0-9a-z]*)\.?([0-9]*)))?({(-?[0-9]+)(,(-?[0-9]+))?})?/', $usage_str, $matches)) { trigger_error("ORM::invalid usage format $usage_str", QN_REPORT_WARNING); } else { diff --git a/lib/equal/orm/usages/UsageAmount.class.php b/lib/equal/orm/usages/UsageAmount.class.php index dd27d90d3..706beb4d8 100644 --- a/lib/equal/orm/usages/UsageAmount.class.php +++ b/lib/equal/orm/usages/UsageAmount.class.php @@ -6,8 +6,7 @@ */ namespace equal\orm\usages; -use equal\locale\Locale; -use core\setting\Setting; +use equal\data\DataGenerator; class UsageAmount extends Usage { @@ -61,4 +60,18 @@ public function getConstraints(): array { ]; } + public function generateRandomValue(): float { + switch($this->getSubtype()) { + case 'money': + case 'percent': + case 'rate': + if($this->getMin() === 0 && $this->getMax() === 0) { + return DataGenerator::realNumberByLength($this->getLength(), $this->getScale()); + } + + return DataGenerator::realNumber($this->getMin(), $this->getMax(), $this->getScale()); + } + return 0; + } + } diff --git a/lib/equal/orm/usages/UsageArray.class.php b/lib/equal/orm/usages/UsageArray.class.php index 23f6200d7..e35c1f576 100644 --- a/lib/equal/orm/usages/UsageArray.class.php +++ b/lib/equal/orm/usages/UsageArray.class.php @@ -26,4 +26,8 @@ public function getConstraints(): array { ]; } + public function generateRandomValue(): array { + return []; + } + } diff --git a/lib/equal/orm/usages/UsageCountry.class.php b/lib/equal/orm/usages/UsageCountry.class.php index b177eda74..3c00c81c9 100644 --- a/lib/equal/orm/usages/UsageCountry.class.php +++ b/lib/equal/orm/usages/UsageCountry.class.php @@ -9,6 +9,12 @@ class UsageCountry extends Usage { + private $numeric_country_codes = ['004','008','010','012','016','020','024','028','031','032','036','040','044','048','050','051','052','056','060','064','068','070','072','074','076','084','086','090','092','096','100','104','108','112','116','120','124','132','136','140','144','148','152','156','158','162','166','170','174','175','178','180','184','188','191','192','196','203','204','208','212','214','218','222','226','231','232','233','234','238','239','242','246','248','250','254','258','260','262','266','268','270','275','','276','288','292','296','300','304','308','312','316','320','324','328','332','334','336','340','344','348','352','356','360','364','368','372','376','380','384','388','392','398','400','404','408','410','414','417','418','422','426','428','430','434','438','440','442','446','450','454','458','462','466','470','474','478','480','484','492','496','498','499','500','504','508','512','516','520','524','528','531','533','534','535','540','548','554','558','562','566','570','574','578','580','581','583','584','585','586','591','598','600','604','608','612','616','620','624','626','630','634','638','642','643','646','652','654','659','660','662','663','666','670','674','678','682','686','688','690','694','702','703','704','705','706','710','716','724','728','729','732','740','744','748','752','756','760','762','764','768','772','776','780','784','788','792','795','796','798','800','804','807','818','826','831','832','833','834','840','850','854','858','860','862','876','882','887','894']; + + private $three_letters_country_codes = ['ABW','AFG','AGO','AIA','ALA','ALB','AND','ARE','ARG','ARM','ASM','ATA','ATF','ATG','AUS','AUT','AZE','BDI','BEL','BEN','BES','BFA','BGD','BGR','BHR','BHS','BIH','BLM','BLR','BLZ','BMU','BOL','BRA','BRB','BRN','BTN','BVT','BWA','CAF','CAN','CCK','CHE','CHL','CHN','CIV','CMR','COD','COG','COK','COL','COM','CPV','CRI','CUB','CUW','CXR','CYM','CYP','CZE','DEU','DJI','DMA','DNK','DOM','DZA','ECU','EGY','ERI','ESH','ESP','EST','ETH','FIN','FJI','FLK','FRA','FRO','FSM','GAB','GBR','GEO','GGY','GHA','GIB','GIN','GLP','GMB','GNB','GNQ','GRC','GRD','GRL','GTM','GUF','GUM','GUY','HKG','HMD','HND','HRV','HTI','HUN','IDN','IMN','IND','IOT','IRL','IRN','IRQ','ISL','ISR','ITA','JAM','JEY','JOR','JPN','KAZ','KEN','KGZ','KHM','KIR','KNA','KOR','KWT','LAO','LBN','LBR','LBY','LCA','LIE','LKA','LSO','LTU','LUX','LVA','MAC','MAF','MAR','MCO','MDA','MDG','MDV','MEX','MHL','MKD','MLI','MLT','MMR','MNE','MNG','MNP','MOZ','MRT','MSR','MTQ','MUS','MWI','MYS','MYT','NAM','NCL','NER','NFK','NGA','NIC','NIU','NLD','NOR','NPL','NRU','NZL','OMN','PAK','PAN','PCN','PER','PHL','PLW','PNG','POL','PRI','PRK','PRT','PRY','PSE','PYF','QAT','REU','ROU','RUS','RWA','SAU','SDN','SEN','SGP','SGS','SHN','SJM','SLB','SLE','SLV','SMR','SOM','SPM','SRB','SSD','STP','SUR','SVK','SVN','SWE','SWZ','SXM','SYC','SYR','TCA','TCD','TGO','THA','TJK','TKL','TKM','TLS','TON','TTO','TUN','TUR','TUV','TWN','TZA','UGA','UKR','UMI','URY','USA','UZB','VAT','VCT','VEN','VGB','VIR','VNM','VUT','WLF','WSM','YEM','ZAF','ZMB','ZWE']; + + private $two_letters_country_codes = ['AF','AX','AL','DZ','AS','AD','AO','AI','AQ','AG','AR','AM','AW','AU','AT','AZ','BS','BH','BD','BB','BY','BE','BZ','BJ','BM','BT','BO','BA','BW','BV','BR','IO','BN','BG','BF','BI','KH','CM','CA','CV','KY','CF','TD','CL','CN','CX','CC','CO','KM','CG','CD','CK','CR','CI','HR','CU','CY','CZ','DK','DJ','DM','DO','EC','EG','SV','GQ','ER','EE','ET','FK','FO','FJ','FI','FR','GF','PF','TF','GA','GM','GE','DE','GH','GI','GR','GL','GD','GP','GU','GT','GG','GN','GW','GY','HT','HM','VA','HN','HK','HU','IS','IN','ID','IR','IQ','IE','IM','IL','IT','JM','JP','JE','JO','KZ','KE','KI','KR','KW','KG','LA','LV','LB','LS','LR','LY','LI','LT','LU','MO','MK','MG','MW','MY','MV','ML','MT','MH','MQ','MR','MU','YT','MX','FM','MD','MC','MN','ME','MS','MA','MZ','MM','NA','NR','NP','NL','AN','NC','NZ','NI','NE','NG','NU','NF','MP','NO','OM','PK','PW','PS','PA','PG','PY','PE','PH','PN','PL','PT','PR','QA','RE','RO','RU','RW','BL','SH','KN','LC','MF','PM','VC','WS','SM','ST','SA','SN','RS','SC','SL','SG','SK','SI','SB','SO','ZA','GS','ES','LK','SD','SR','SJ','SZ','SE','CH','SY','TW','TJ','TZ','TH','TL','TG','TK','TO','TT','TN','TR','TM','TC','TV','UG','UA','AE','GB','US','UM','UY','UZ','VU','VE','VN','VG','VI','WF','EH','YE','ZM','ZW']; + public function __construct(string $usage_str) { parent::__construct($usage_str); if($this->length == 0) { @@ -22,7 +28,7 @@ public function getConstraints(): array { 'invalid_country' => [ 'message' => 'Value is not a 3-digits country code (iso-3166-1 numeric).', 'function' => function($value) { - return (in_array($value, ['004','008','010','012','016','020','024','028','031','032','036','040','044','048','050','051','052','056','060','064','068','070','072','074','076','084','086','090','092','096','100','104','108','112','116','120','124','132','136','140','144','148','152','156','158','162','166','170','174','175','178','180','184','188','191','192','196','203','204','208','212','214','218','222','226','231','232','233','234','238','239','242','246','248','250','254','258','260','262','266','268','270','275','','276','288','292','296','300','304','308','312','316','320','324','328','332','334','336','340','344','348','352','356','360','364','368','372','376','380','384','388','392','398','400','404','408','410','414','417','418','422','426','428','430','434','438','440','442','446','450','454','458','462','466','470','474','478','480','484','492','496','498','499','500','504','508','512','516','520','524','528','531','533','534','535','540','548','554','558','562','566','570','574','578','580','581','583','584','585','586','591','598','600','604','608','612','616','620','624','626','630','634','638','642','643','646','652','654','659','660','662','663','666','670','674','678','682','686','688','690','694','702','703','704','705','706','710','716','724','728','729','732','740','744','748','752','756','760','762','764','768','772','776','780','784','788','792','795','796','798','800','804','807','818','826','831','832','833','834','840','850','854','858','860','862','876','882','887','894'])); + return (in_array($value, $this->numeric_country_codes)); } ] ]; @@ -34,7 +40,7 @@ public function getConstraints(): array { 'invalid_country' => [ 'message' => 'Value is not a 3-letters country code (iso-3166-1 alpha-3).', 'function' => function($value) { - return (in_array($value, ['ABW','AFG','AGO','AIA','ALA','ALB','AND','ARE','ARG','ARM','ASM','ATA','ATF','ATG','AUS','AUT','AZE','BDI','BEL','BEN','BES','BFA','BGD','BGR','BHR','BHS','BIH','BLM','BLR','BLZ','BMU','BOL','BRA','BRB','BRN','BTN','BVT','BWA','CAF','CAN','CCK','CHE','CHL','CHN','CIV','CMR','COD','COG','COK','COL','COM','CPV','CRI','CUB','CUW','CXR','CYM','CYP','CZE','DEU','DJI','DMA','DNK','DOM','DZA','ECU','EGY','ERI','ESH','ESP','EST','ETH','FIN','FJI','FLK','FRA','FRO','FSM','GAB','GBR','GEO','GGY','GHA','GIB','GIN','GLP','GMB','GNB','GNQ','GRC','GRD','GRL','GTM','GUF','GUM','GUY','HKG','HMD','HND','HRV','HTI','HUN','IDN','IMN','IND','IOT','IRL','IRN','IRQ','ISL','ISR','ITA','JAM','JEY','JOR','JPN','KAZ','KEN','KGZ','KHM','KIR','KNA','KOR','KWT','LAO','LBN','LBR','LBY','LCA','LIE','LKA','LSO','LTU','LUX','LVA','MAC','MAF','MAR','MCO','MDA','MDG','MDV','MEX','MHL','MKD','MLI','MLT','MMR','MNE','MNG','MNP','MOZ','MRT','MSR','MTQ','MUS','MWI','MYS','MYT','NAM','NCL','NER','NFK','NGA','NIC','NIU','NLD','NOR','NPL','NRU','NZL','OMN','PAK','PAN','PCN','PER','PHL','PLW','PNG','POL','PRI','PRK','PRT','PRY','PSE','PYF','QAT','REU','ROU','RUS','RWA','SAU','SDN','SEN','SGP','SGS','SHN','SJM','SLB','SLE','SLV','SMR','SOM','SPM','SRB','SSD','STP','SUR','SVK','SVN','SWE','SWZ','SXM','SYC','SYR','TCA','TCD','TGO','THA','TJK','TKL','TKM','TLS','TON','TTO','TUN','TUR','TUV','TWN','TZA','UGA','UKR','UMI','URY','USA','UZB','VAT','VCT','VEN','VGB','VIR','VNM','VUT','WLF','WSM','YEM','ZAF','ZMB','ZWE'])); + return (in_array($value, $this->three_letters_country_codes)); } ] ]; @@ -45,11 +51,25 @@ public function getConstraints(): array { 'invalid_country' => [ 'message' => 'Value is not a 2-letters country code (iso-3166-1 alpha-2).', 'function' => function($value) { - return (in_array($value, ['AF','AX','AL','DZ','AS','AD','AO','AI','AQ','AG','AR','AM','AW','AU','AT','AZ','BS','BH','BD','BB','BY','BE','BZ','BJ','BM','BT','BO','BA','BW','BV','BR','IO','BN','BG','BF','BI','KH','CM','CA','CV','KY','CF','TD','CL','CN','CX','CC','CO','KM','CG','CD','CK','CR','CI','HR','CU','CY','CZ','DK','DJ','DM','DO','EC','EG','SV','GQ','ER','EE','ET','FK','FO','FJ','FI','FR','GF','PF','TF','GA','GM','GE','DE','GH','GI','GR','GL','GD','GP','GU','GT','GG','GN','GW','GY','HT','HM','VA','HN','HK','HU','IS','IN','ID','IR','IQ','IE','IM','IL','IT','JM','JP','JE','JO','KZ','KE','KI','KR','KW','KG','LA','LV','LB','LS','LR','LY','LI','LT','LU','MO','MK','MG','MW','MY','MV','ML','MT','MH','MQ','MR','MU','YT','MX','FM','MD','MC','MN','ME','MS','MA','MZ','MM','NA','NR','NP','NL','AN','NC','NZ','NI','NE','NG','NU','NF','MP','NO','OM','PK','PW','PS','PA','PG','PY','PE','PH','PN','PL','PT','PR','QA','RE','RO','RU','RW','BL','SH','KN','LC','MF','PM','VC','WS','SM','ST','SA','SN','RS','SC','SL','SG','SK','SI','SB','SO','ZA','GS','ES','LK','SD','SR','SJ','SZ','SE','CH','SY','TW','TJ','TZ','TH','TL','TG','TK','TO','TT','TN','TR','TM','TC','TV','UG','UA','AE','GB','US','UM','UY','UZ','VU','VE','VN','VG','VI','WF','EH','YE','ZM','ZW'])); + return (in_array($value, $this->two_letters_country_codes)); } ] ]; } } + public function generateRandomValue(): string { + if($this->getSubtype() == 'country.numeric') { + $country_identifiers = $this->numeric_country_codes; + } + elseif($this->getLength() === 3) { + $country_identifiers = $this->three_letters_country_codes; + } + else { + $country_identifiers = $this->two_letters_country_codes; + } + + return $country_identifiers[array_rand($country_identifiers)]; + } + } diff --git a/lib/equal/orm/usages/UsageCurrency.class.php b/lib/equal/orm/usages/UsageCurrency.class.php index 77f88f410..151d7bdf6 100644 --- a/lib/equal/orm/usages/UsageCurrency.class.php +++ b/lib/equal/orm/usages/UsageCurrency.class.php @@ -9,6 +9,10 @@ class UsageCurrency extends Usage { + private $three_digits_country_codes = ['004','008','010','012','016','020','024','028','031','032','036','040','044','048','050','051','052','056','060','064','068','070','072','074','076','084','086','090','092','096','100','104','108','112','116','120','124','132','136','140','144','148','152','156','158','162','166','170','174','175','178','180','184','188','191','192','196','203','204','208','212','214','218','222','226','231','232','233','234','238','239','242','246','248','250','254','258','260','262','266','268','270','275','','276','288','292','296','300','304','308','312','316','320','324','328','332','334','336','340','344','348','352','356','360','364','368','372','376','380','384','388','392','398','400','404','408','410','414','417','418','422','426','428','430','434','438','440','442','446','450','454','458','462','466','470','474','478','480','484','492','496','498','499','500','504','508','512','516','520','524','528','531','533','534','535','540','548','554','558','562','566','570','574','578','580','581','583','584','585','586','591','598','600','604','608','612','616','620','624','626','630','634','638','642','643','646','652','654','659','660','662','663','666','670','674','678','682','686','688','690','694','702','703','704','705','706','710','716','724','728','729','732','740','744','748','752','756','760','762','764','768','772','776','780','784','788','792','795','796','798','800','804','807','818','826','831','832','833','834','840','850','854','858','860','862','876','882','887','894']; + + private $three_letters_currency_codes = ['ADF','ADP','AED','AFA','AFN','ALL','AMD','ANG','AOA','AOK','AON','AOR','ARP','ARS','ATS','AUD','AWG','AZM','AZN','BAM','BBD','BDT','BEF','BGL','BGN','BHD','BIF','BMD','BND','BOB','BOP','BOV','BRL','BRR','BSD','BTN','BWP','BYB','BYR','BYN','BZD','CAD','CDF','CHE','CHF','CHW','CLF','CLP','CNY','COP','COU','CRC','CSD','CSK','CUC','CUP','CVE','CYP','CZK','DEM','DJF','DKK','DOP','DZD','ECS','ECV','EEK','EGP','ERN','ESP','ETB','EUR','FIM','FJD','FKP','FRF','GBP','GEL','GHS','GIP','GMD','GNF','GRD','GTQ','GWP','GYD','HKD','HNL','HRK','HTG','HUF','IDR','IEP','ILS','INR','IQD','IRR','ISK','ITL','JMD','JOD','JPY','KES','KGS','KHR','KMF','KPW','KRW','KZT','KWD','KYD','LAK','LBP','LKR','LRD','LSL','LTL','LUF','LVL','LVR','LYD','MAD','MDL','MGA','MGF','MKD','MMK','MNT','MOP','MRO','MRU','MTL','MUR','MVR','MWK','MXN','MXV','MYR','MZE','MZM','MZN','NAD','NGN','NHF','NIC','NIO','NLG','NOK','NPR','NZD','OMR','PAB','PEN','PES','PGK','PHP','PKR','PLN','PLZ','PTE','PYG','QAR','ROL','RON','RSD','RUB','RWF','SAR','SBD','SCR','SDD','SDG','SDP','SEK','SGD','SHP','SIT','SKK','SLL','SML','SOS','SRD','SSP','STD','SUB','SUR','SVC','SYP','SZL','THB','TJS','TMM','TMT','TND','TOP','TPE','TRL','TRY','TTD','TWD','TZS','UAH','UGX','USD','USN','USS','UYU','UYW','UZS','VAL','VEB','VEF','VES','VND','VUV','WST','XAF','XAG','XAU','XBA','XBB','XBC','XBD','XCD','XDR','XEU','XFO','XFU','XOF','XPD','XPF','XPT','XSU','XUA','YER','YUD','YUM','ZAR','ZMK','ZWD','ZWL','ZWR']; + public function __construct(string $usage_str) { parent::__construct($usage_str); if($this->length == 0) { @@ -22,7 +26,7 @@ public function getConstraints(): array { 'invalid_currency' => [ 'message' => 'Value is not a 3-digits country code (as of iso-3166-1).', 'function' => function($value) { - return (in_array($value, ['004','008','010','012','016','020','024','028','031','032','036','040','044','048','050','051','052','056','060','064','068','070','072','074','076','084','086','090','092','096','100','104','108','112','116','120','124','132','136','140','144','148','152','156','158','162','166','170','174','175','178','180','184','188','191','192','196','203','204','208','212','214','218','222','226','231','232','233','234','238','239','242','246','248','250','254','258','260','262','266','268','270','275','','276','288','292','296','300','304','308','312','316','320','324','328','332','334','336','340','344','348','352','356','360','364','368','372','376','380','384','388','392','398','400','404','408','410','414','417','418','422','426','428','430','434','438','440','442','446','450','454','458','462','466','470','474','478','480','484','492','496','498','499','500','504','508','512','516','520','524','528','531','533','534','535','540','548','554','558','562','566','570','574','578','580','581','583','584','585','586','591','598','600','604','608','612','616','620','624','626','630','634','638','642','643','646','652','654','659','660','662','663','666','670','674','678','682','686','688','690','694','702','703','704','705','706','710','716','724','728','729','732','740','744','748','752','756','760','762','764','768','772','776','780','784','788','792','795','796','798','800','804','807','818','826','831','832','833','834','840','850','854','858','860','862','876','882','887','894'])); + return (in_array($value, $this->three_digits_country_codes)); } ] ]; @@ -34,13 +38,21 @@ public function getConstraints(): array { default: return [ 'invalid_currency' => [ - 'message' => 'Value is not a 2-letters language code (as of iso-4217 alpha-3).', + 'message' => 'Value is not a 3-letters currency code (as of iso-4217 alpha-3).', 'function' => function($value) { - return (in_array($value, ['ADF','ADP','AED','AFA','AFN','ALL','AMD','ANG','AOA','AOK','AON','AOR','ARP','ARS','ATS','AUD','AWG','AZM','AZN','BAM','BBD','BDT','BEF','BGL','BGN','BHD','BIF','BMD','BND','BOB','BOP','BOV','BRL','BRR','BSD','BTN','BWP','BYB','BYR','BYN','BZD','CAD','CDF','CHE','CHF','CHW','CLF','CLP','CNY','COP','COU','CRC','CSD','CSK','CUC','CUP','CVE','CYP','CZK','DEM','DJF','DKK','DOP','DZD','ECS','ECV','EEK','EGP','ERN','ESP','ETB','EUR','FIM','FJD','FKP','FRF','GBP','GEL','GHS','GIP','GMD','GNF','GRD','GTQ','GWP','GYD','HKD','HNL','HRK','HTG','HUF','IDR','IEP','ILS','INR','IQD','IRR','ISK','ITL','JMD','JOD','JPY','KES','KGS','KHR','KMF','KPW','KRW','KZT','KWD','KYD','LAK','LBP','LKR','LRD','LSL','LTL','LUF','LVL','LVR','LYD','MAD','MDL','MGA','MGF','MKD','MMK','MNT','MOP','MRO','MRU','MTL','MUR','MVR','MWK','MXN','MXV','MYR','MZE','MZM','MZN','NAD','NGN','NHF','NIC','NIO','NLG','NOK','NPR','NZD','OMR','PAB','PEN','PES','PGK','PHP','PKR','PLN','PLZ','PTE','PYG','QAR','ROL','RON','RSD','RUB','RWF','SAR','SBD','SCR','SDD','SDG','SDP','SEK','SGD','SHP','SIT','SKK','SLL','SML','SOS','SRD','SSP','STD','SUB','SUR','SVC','SYP','SZL','THB','TJS','TMM','TMT','TND','TOP','TPE','TRL','TRY','TTD','TWD','TZS','UAH','UGX','USD','USN','USS','UYU','UYW','UZS','VAL','VEB','VEF','VES','VND','VUV','WST','XAF','XAG','XAU','XBA','XBB','XBC','XBD','XCD','XDR','XEU','XFO','XFU','XOF','XPD','XPF','XPT','XSU','XUA','YER','YUD','YUM','ZAR','ZMK','ZWD','ZWL','ZWR'])); + return (in_array($value, $this->three_letters_currency_codes)); } ] ]; } } + public function generateRandomValue(): string { + if($this->getSubtype() == 'iso-4217.numeric') { + return $this->three_digits_country_codes[array_rand($this->three_digits_country_codes)]; + } + + return $this->three_letters_currency_codes[array_rand($this->three_letters_currency_codes)]; + } + } diff --git a/lib/equal/orm/usages/UsageDate.class.php b/lib/equal/orm/usages/UsageDate.class.php index afdeb5547..b53dbb244 100644 --- a/lib/equal/orm/usages/UsageDate.class.php +++ b/lib/equal/orm/usages/UsageDate.class.php @@ -79,4 +79,30 @@ public function getConstraints(): array { return $constraints; } + public function generateRandomValue(): int { + switch($this->getSubtype(0)) { + case 'day': + return mt_rand(1, 31); + case 'weekday.mon': + return mt_rand(1, 7); + case 'weekday.sun': + return mt_rand(0, 6); + case 'month': + return mt_rand(1, 12); + case 'year': + return mt_rand(1, 9999); + case 'yearweek': + return mt_rand(1, 52); + case 'yearday': + return mt_rand(1, 365); + case 'time': + case 'plain': + default: + $start_timestamp = strtotime('1970-01-01 00:00:00'); + $end_timestamp = time(); + + return mt_rand($start_timestamp, $end_timestamp); + } + } + } diff --git a/lib/equal/orm/usages/UsageEmail.class.php b/lib/equal/orm/usages/UsageEmail.class.php index 3623190d6..5e5eb9840 100644 --- a/lib/equal/orm/usages/UsageEmail.class.php +++ b/lib/equal/orm/usages/UsageEmail.class.php @@ -7,6 +7,8 @@ namespace equal\orm\usages; +use equal\data\DataGenerator; + class UsageEmail extends Usage { public function __construct(string $usage_str) { @@ -39,4 +41,8 @@ public function getConstraints(): array { ]; } + public function generateRandomValue(): string { + return DataGenerator::email(); + } + } diff --git a/lib/equal/orm/usages/UsageLanguage.class.php b/lib/equal/orm/usages/UsageLanguage.class.php index e86272c93..90ead5142 100644 --- a/lib/equal/orm/usages/UsageLanguage.class.php +++ b/lib/equal/orm/usages/UsageLanguage.class.php @@ -9,6 +9,10 @@ class UsageLanguage extends Usage { + private $three_letters_langue_codes = ['aar','abk','ace','ach','ada','ady','afa','afh','afr','ain','aka','akk','alb','sqi','ale','alg','alt','amh','ang','anp','apa','ara','arc','arg','arm','hye','arn','arp','art','arw','asm','ast','ath','aus','ava','ave','awa','aym','aze','bad','bai','bak','bal','bam','ban','baq','eus','bas','bat','bej','bel','bem','ben','ber','bho','bih','bik','bin','bis','bla','bnt','tib','bod','bos','bra','bre','btk','bua','bug','bul','bur','mya','byn','cad','cai','car','cat','cau','ceb','cel','cze','ces','cha','chb','che','chg','chi','zho','chk','chm','chn','cho','chp','chr','chu','chv','chy','cmc','cnr','cop','cor','cos','cpe','cpf','cpp','cre','crh','crp','csb','cus','wel','cym','cze','ces','dak','dan','dar','day','del','den','ger','deu','dgr','din','div','doi','dra','dsb','dua','dum','dut','nld','dyu','dzo','efi','egy','eka','gre','ell','elx','eng','enm','epo','est','baq','eus','ewe','ewo','fan','fao','per','fas','fat','fij','fil','fin','fiu','fon','fra','fre','frm','fro','frr','frs','fry','ful','fur','gaa','gay','gba','gem','geo','kat','ger','deu','gez','gil','gla','gle','glg','glv','gmh','goh','gon','gor','got','grb','grc','gre','ell','grn','gsw','guj','gwi','hai','hat','hau','haw','heb','her','hil','him','hin','hit','hmn','hmo','hrv','hsb','hun','hup','arm','hye','iba','ibo','ice','isl','ido','iii','ijo','iku','ile','ilo','ina','inc','ind','ine','inh','ipk','ira','iro','ice','isl','ita','jav','jbo','jpn','jpr','jrb','kaa','kab','kac','kal','kam','kan','kar','kas','geo','kat','kau','kaw','kaz','kbd','kha','khi','khm','kho','kik','kin','kir','kmb','kok','kom','kon','kor','kos','kpe','krc','krl','kro','kru','kua','kum','kur','kut','lad','lah','lam','lao','lat','lav','lez','lim','lin','lit','lol','loz','ltz','lua','lub','lug','lui','lun','luo','lus','mac','mkd','mad','mag','mah','mai','mak','mal','man','mao','mri','map','mar','mas','may','msa','mdf','mdr','men','mga','mic','min','mis','mac','mkd','mkh','mlg','mlt','mnc','mni','mno','moh','mon','mos','mao','mri','may','msa','mul','mun','mus','mwl','mwr','bur','mya','myn','myv','nah','nai','nap','nau','nav','nbl','nde','ndo','nds','nep','new','nia','nic','niu','dut','nld','nno','nob','nog','non','nor','nqo','nso','nub','nwc','nya','nym','nyn','nyo','nzi','oci','oji','ori','orm','osa','oss','ota','oto','paa','pag','pal','pam','pan','pap','pau','peo','per','fas','phi','phn','pli','pol','pon','por','pra','pro','pus','qaa','que','raj','rap','rar','roa','roh','rom','rum','ron','rum','ron','run','rup','rus','sad','sag','sah','sai','sal','sam','san','sas','sat','scn','sco','sel','sem','sga','sgn','shn','sid','sin','sio','sit','sla','slo','slk','slo','slk','slv','sma','sme','smi','smj','smn','smo','sms','sna','snd','snk','sog','som','son','sot','spa','alb','sqi','srd','srn','srp','srr','ssa','ssw','suk','sun','sus','sux','swa','swe','syc','syr','tah','tai','tam','tat','tel','tem','ter','tet','tgk','tgl','tha','tib','bod','tig','tir','tiv','tkl','tlh','tli','tmh','tog','ton','tpi','tsi','tsn','tso','tuk','tum','tup','tur','tut','tvl','twi','tyv','udm','uga','uig','ukr','umb','und','urd','uzb','vai','ven','vie','vol','vot','wak','wal','war','was','wel','cym','wen','wln','wol','xal','xho','yao','yap','yid','yor','ypk','zap','zbl','zen','zgh','zha','chi','zho','znd','zul','zun','zxx','zza']; + + private $two_letters_language_codes = ['aa','ab','af','ak','sq','am','ar','an','hy','as','av','ae','ay','az','ba','bm','eu','be','bn','bh','bi','bo','bs','br','bg','my','ca','cs','ch','ce','zh','cu','cv','kw','co','cr','cy','cs','da','de','dv','nl','dz','el','en','eo','et','eu','ee','fo','fa','fj','fi','fr','fy','ff','ka','de','gd','ga','gl','gv','el','gn','gu','ht','ha','he','hz','hi','ho','hr','hu','hy','ig','is','io','ii','iu','ie','ia','id','ik','is','it','jv','ja','kl','kn','ks','ka','kr','kk','km','ki','rw','ky','kv','kg','ko','kj','ku','lo','la','lv','li','ln','lt','lb','lu','lg','mk','mh','ml','mi','mr','ms','mk','mg','mt','mn','mi','ms','my','na','nv','nr','nd','ng','ne','nl','nn','nb','no','ny','oc','oj','or','om','os','pa','fa','pi','pl','pt','ps','qu','rm','ro','ro','rn','ru','sg','sa','si','sk','sk','sl','se','sm','sn','sd','so','st','es','sq','sc','sr','ss','su','sw','sv','ty','ta','tt','te','tg','tl','th','bo','ti','to','tn','ts','tk','tr','tw','ug','uk','ur','uz','ve','vi','vo','cy','wa','wo','xh','yi','yo','za','zh','zu']; + public function __construct(string $usage_str) { parent::__construct($usage_str); if($this->length == 0) { @@ -24,7 +28,7 @@ public function getConstraints(): array { 'invalid_language' => [ 'message' => 'Value is not a 3-letters language code (iso-639-2 alpha-3).', 'function' => function($value) { - return (in_array($value, ['aar','abk','ace','ach','ada','ady','afa','afh','afr','ain','aka','akk','alb','sqi','ale','alg','alt','amh','ang','anp','apa','ara','arc','arg','arm','hye','arn','arp','art','arw','asm','ast','ath','aus','ava','ave','awa','aym','aze','bad','bai','bak','bal','bam','ban','baq','eus','bas','bat','bej','bel','bem','ben','ber','bho','bih','bik','bin','bis','bla','bnt','tib','bod','bos','bra','bre','btk','bua','bug','bul','bur','mya','byn','cad','cai','car','cat','cau','ceb','cel','cze','ces','cha','chb','che','chg','chi','zho','chk','chm','chn','cho','chp','chr','chu','chv','chy','cmc','cnr','cop','cor','cos','cpe','cpf','cpp','cre','crh','crp','csb','cus','wel','cym','cze','ces','dak','dan','dar','day','del','den','ger','deu','dgr','din','div','doi','dra','dsb','dua','dum','dut','nld','dyu','dzo','efi','egy','eka','gre','ell','elx','eng','enm','epo','est','baq','eus','ewe','ewo','fan','fao','per','fas','fat','fij','fil','fin','fiu','fon','fra','fre','frm','fro','frr','frs','fry','ful','fur','gaa','gay','gba','gem','geo','kat','ger','deu','gez','gil','gla','gle','glg','glv','gmh','goh','gon','gor','got','grb','grc','gre','ell','grn','gsw','guj','gwi','hai','hat','hau','haw','heb','her','hil','him','hin','hit','hmn','hmo','hrv','hsb','hun','hup','arm','hye','iba','ibo','ice','isl','ido','iii','ijo','iku','ile','ilo','ina','inc','ind','ine','inh','ipk','ira','iro','ice','isl','ita','jav','jbo','jpn','jpr','jrb','kaa','kab','kac','kal','kam','kan','kar','kas','geo','kat','kau','kaw','kaz','kbd','kha','khi','khm','kho','kik','kin','kir','kmb','kok','kom','kon','kor','kos','kpe','krc','krl','kro','kru','kua','kum','kur','kut','lad','lah','lam','lao','lat','lav','lez','lim','lin','lit','lol','loz','ltz','lua','lub','lug','lui','lun','luo','lus','mac','mkd','mad','mag','mah','mai','mak','mal','man','mao','mri','map','mar','mas','may','msa','mdf','mdr','men','mga','mic','min','mis','mac','mkd','mkh','mlg','mlt','mnc','mni','mno','moh','mon','mos','mao','mri','may','msa','mul','mun','mus','mwl','mwr','bur','mya','myn','myv','nah','nai','nap','nau','nav','nbl','nde','ndo','nds','nep','new','nia','nic','niu','dut','nld','nno','nob','nog','non','nor','nqo','nso','nub','nwc','nya','nym','nyn','nyo','nzi','oci','oji','ori','orm','osa','oss','ota','oto','paa','pag','pal','pam','pan','pap','pau','peo','per','fas','phi','phn','pli','pol','pon','por','pra','pro','pus','qaa','que','raj','rap','rar','roa','roh','rom','rum','ron','rum','ron','run','rup','rus','sad','sag','sah','sai','sal','sam','san','sas','sat','scn','sco','sel','sem','sga','sgn','shn','sid','sin','sio','sit','sla','slo','slk','slo','slk','slv','sma','sme','smi','smj','smn','smo','sms','sna','snd','snk','sog','som','son','sot','spa','alb','sqi','srd','srn','srp','srr','ssa','ssw','suk','sun','sus','sux','swa','swe','syc','syr','tah','tai','tam','tat','tel','tem','ter','tet','tgk','tgl','tha','tib','bod','tig','tir','tiv','tkl','tlh','tli','tmh','tog','ton','tpi','tsi','tsn','tso','tuk','tum','tup','tur','tut','tvl','twi','tyv','udm','uga','uig','ukr','umb','und','urd','uzb','vai','ven','vie','vol','vot','wak','wal','war','was','wel','cym','wen','wln','wol','xal','xho','yao','yap','yid','yor','ypk','zap','zbl','zen','zgh','zha','chi','zho','znd','zul','zun','zxx','zza'])); + return (in_array($value, $this->three_letters_langue_codes)); } ] ]; @@ -35,11 +39,17 @@ public function getConstraints(): array { 'invalid_language' => [ 'message' => 'Value is not a 2-letters language code (iso-639-1 alpha-2).', 'function' => function($value) { - return (in_array($value, ['aa','ab','af','ak','sq','am','ar','an','hy','as','av','ae','ay','az','ba','bm','eu','be','bn','bh','bi','bo','bs','br','bg','my','ca','cs','ch','ce','zh','cu','cv','kw','co','cr','cy','cs','da','de','dv','nl','dz','el','en','eo','et','eu','ee','fo','fa','fj','fi','fr','fy','ff','ka','de','gd','ga','gl','gv','el','gn','gu','ht','ha','he','hz','hi','ho','hr','hu','hy','ig','is','io','ii','iu','ie','ia','id','ik','is','it','jv','ja','kl','kn','ks','ka','kr','kk','km','ki','rw','ky','kv','kg','ko','kj','ku','lo','la','lv','li','ln','lt','lb','lu','lg','mk','mh','ml','mi','mr','ms','mk','mg','mt','mn','mi','ms','my','na','nv','nr','nd','ng','ne','nl','nn','nb','no','ny','oc','oj','or','om','os','pa','fa','pi','pl','pt','ps','qu','rm','ro','ro','rn','ru','sg','sa','si','sk','sk','sl','se','sm','sn','sd','so','st','es','sq','sc','sr','ss','su','sw','sv','ty','ta','tt','te','tg','tl','th','bo','ti','to','tn','ts','tk','tr','tw','ug','uk','ur','uz','ve','vi','vo','cy','wa','wo','xh','yi','yo','za','zh','zu'])); + return (in_array($value, $this->two_letters_language_codes)); } ] ]; } } + public function generateRandomValue(): string { + $language_codes = $this->getLength() === 3 ? $this->three_letters_langue_codes : $this->two_letters_language_codes; + + return $language_codes[array_rand($language_codes)]; + } + } diff --git a/lib/equal/orm/usages/UsageNumber.class.php b/lib/equal/orm/usages/UsageNumber.class.php index 85ed4754e..0e35b7af8 100644 --- a/lib/equal/orm/usages/UsageNumber.class.php +++ b/lib/equal/orm/usages/UsageNumber.class.php @@ -6,7 +6,7 @@ */ namespace equal\orm\usages; -use equal\locale\Locale; +use equal\data\DataGenerator; class UsageNumber extends Usage { @@ -56,6 +56,18 @@ public function getConstraints(): array { 'function' => function($value) { return preg_match('/^[+-]?[0-9]{0,'.$this->getLength().'}$/', (string) $value); } + ], + 'too_low' => [ + 'message' => 'Value is too low.', + 'function' => function($value) { + return (!$this->getMin() && !$this->getMax()) || $value >= $this->getMin(); + } + ], + 'too_high' => [ + 'message' => 'Value is too high.', + 'function' => function($value) { + return (!$this->getMin() && !$this->getMax()) || $value <= $this->getMax(); + } ] ]; case 'natural': @@ -65,6 +77,18 @@ public function getConstraints(): array { 'function' => function($value) { return preg_match('/^[0-9]{0,'.$this->getLength().'}$/', (string) $value); } + ], + 'too_low' => [ + 'message' => 'Value is too low.', + 'function' => function($value) { + return (!$this->getMin() && !$this->getMax()) || $value >= $this->getMin(); + } + ], + 'too_high' => [ + 'message' => 'Value is too high.', + 'function' => function($value) { + return (!$this->getMin() && !$this->getMax()) || $value <= $this->getMax(); + } ] ]; case 'real': @@ -84,6 +108,18 @@ public function getConstraints(): array { $decimals = $this->getScale(); return preg_match('/^[+-]?[0-9]{0,'.$integers.'}(\.[0-9]{1,'.$decimals.'})?$/', (string) $value); } + ], + 'too_low' => [ + 'message' => 'Value is too low.', + 'function' => function($value) { + return (!$this->getMin() && !$this->getMax()) || $value >= $this->getMin(); + } + ], + 'too_high' => [ + 'message' => 'Value is too high.', + 'function' => function($value) { + return (!$this->getMin() && !$this->getMax()) || $value <= $this->getMax(); + } ] ]; default: @@ -91,4 +127,30 @@ public function getConstraints(): array { } } + /** + * @return bool|float|int|string|null + */ + public function generateRandomValue() { + switch($this->getSubtype(0)) { + case 'boolean': + return DataGenerator::boolean(); + case 'integer': + if($this->getMin() === 0 && $this->getMax() === 0) { + return DataGenerator::integerByLength($this->getLength()); + } + + return DataGenerator::integer($this->getMin(), $this->getMax()); + case 'real': + if($this->getMin() === 0 && $this->getMax() === 0) { + return DataGenerator::realNumberByLength($this->getLength(), $this->getScale()); + } + + return DataGenerator::realNumber($this->getMin(), $this->getMax(), $this->getScale()); + case 'hexadecimal': + return DataGenerator::hexadecimal($this->getLength()); + } + + return 0; + } + } diff --git a/lib/equal/orm/usages/UsagePassword.class.php b/lib/equal/orm/usages/UsagePassword.class.php index e81575472..88862a073 100644 --- a/lib/equal/orm/usages/UsagePassword.class.php +++ b/lib/equal/orm/usages/UsagePassword.class.php @@ -47,4 +47,15 @@ public function getConstraints(): array { } } + public function generateRandomValue(): string { + $password = ''; + $dict = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'; + $len = strlen($dict) - 1; + for ($i = 0; $i < 10; $i++) { + $password .= $dict[mt_rand(0, $len)]; + } + + return $password; + } + } diff --git a/lib/equal/orm/usages/UsagePhone.class.php b/lib/equal/orm/usages/UsagePhone.class.php index 8af6dd26d..13aa598bd 100644 --- a/lib/equal/orm/usages/UsagePhone.class.php +++ b/lib/equal/orm/usages/UsagePhone.class.php @@ -7,6 +7,8 @@ namespace equal\orm\usages; +use equal\data\DataGenerator; + class UsagePhone extends Usage { public function __construct(string $usage_str) { @@ -27,4 +29,8 @@ public function getConstraints(): array { ]; } + public function generateRandomValue(): string { + return DataGenerator::phoneNumberE164(); + } + } diff --git a/lib/equal/orm/usages/UsageText.class.php b/lib/equal/orm/usages/UsageText.class.php index 1b449300a..8fa3d7f01 100644 --- a/lib/equal/orm/usages/UsageText.class.php +++ b/lib/equal/orm/usages/UsageText.class.php @@ -6,6 +6,8 @@ */ namespace equal\orm\usages; +use equal\data\DataGenerator; + class UsageText extends Usage { /** @@ -100,4 +102,13 @@ public function getConstraints(): array { ]; } + public function generateRandomValue(): string { + $max = $this->getMax(); + if($max === 0) { + $max = $this->getLength(); + } + + return DataGenerator::plainText($this->getMin(), $max); + } + } diff --git a/lib/equal/orm/usages/UsageTime.php b/lib/equal/orm/usages/UsageTime.php index f76a0c02d..dbc3d692b 100644 --- a/lib/equal/orm/usages/UsageTime.php +++ b/lib/equal/orm/usages/UsageTime.php @@ -6,8 +6,7 @@ */ namespace equal\orm\usages; -use equal\locale\Locale; -use core\setting\Setting; +use equal\data\DataGenerator; class UsageTime extends Usage { @@ -23,4 +22,8 @@ public function getConstraints(): array { return []; } + public function generateRandomValue(): string { + return DataGenerator::time(); + } + } diff --git a/lib/equal/orm/usages/UsageUri.class.php b/lib/equal/orm/usages/UsageUri.class.php index d8e014db8..84502fc8d 100644 --- a/lib/equal/orm/usages/UsageUri.class.php +++ b/lib/equal/orm/usages/UsageUri.class.php @@ -7,6 +7,8 @@ namespace equal\orm\usages; +use equal\data\DataGenerator; + class UsageUri extends Usage { public function __construct(string $usage_str) { @@ -45,12 +47,13 @@ public function getConstraints(): array { file://localhost/path ircs://irc.example.com:6697/#channel1,#channel2 https://username:password@example.com:443 + https://discope.yb.run/support/#/ticket/833 */ return [ 'invalid_url' => [ 'message' => 'String is not a valid URL.', 'function' => function($value) { - return (bool) (preg_match('/^((([a-zA-Z][a-zA-Z0-9+.-]*):)?\/\/)?(([a-zA-Z0-9.-]+)(:[a-zA-Z0-9.-]+)?@)?([a-zA-Z0-9.-]+|\[[0-9:.]+\])(:[0-9]{1,5})?(\/[a-zA-Z0-9\/%._=,]*)?(\?[a-zA-Z0-9&=%._-]*)?(#[a-zA-Z0-9,%-]*)*$/', $value)); + return (bool) (preg_match('/^((([a-zA-Z][a-zA-Z0-9+.-]*):)?\/\/)?(([a-zA-Z0-9.-]+)(:[a-zA-Z0-9.-]+)?@)?([a-zA-Z0-9.-]+|\[[0-9:.]+\])(:[0-9]{1,5})?(\/[a-zA-Z0-9\/%._=,]*)?(\?[a-zA-Z0-9&=%._-]*)?(#[a-zA-Z0-9,%-\/]*)*$/', $value)); } ] ]; @@ -94,4 +97,22 @@ public function getConstraints(): array { return []; } + public function generateRandomValue(): string { + switch($this->getSubtype()) { + case 'url.relative': + return DataGenerator::relativeUrl(); + case 'url.tel': + return DataGenerator::urlTel(); + case 'url.mailto': + return DataGenerator::urlMailto(); + case 'urn.iban': + return DataGenerator::iban(); + case 'urn.ean': + return DataGenerator::ean13(); + case 'url': + default: + return DataGenerator::url(); + } + } + } diff --git a/packages/core/actions/init/anonymize.php b/packages/core/actions/init/anonymize.php new file mode 100644 index 000000000..8e0d1d3c9 --- /dev/null +++ b/packages/core/actions/init/anonymize.php @@ -0,0 +1,77 @@ + + Some Rights Reserved, The eQual Framework, 2010-2024 + Author: The eQual Framework Contributors + Original Author: Lucas Laurent + Licensed under GNU LGPL 3 license +*/ + +list($params, $providers) = eQual::announce([ + 'description' => 'Anonymize objects using json configuration files in "{package}/init/anonymize/".', + 'params' => [ + 'package' => [ + 'description' => 'Name of the package to anonymize.', + 'type' => 'string', + 'usage' => 'orm/package', + 'required' => true + ], + 'config_file' => [ + 'description' => 'Name of the configuration file to use to anonymize objects.', + 'help' => 'Configuration file must match the format "{package}/init/anonymize/{config_file}.json".' + . ' If no config file specified, then all files of anonymize folder are used.', + 'type' => 'string' + ] + ], + 'response' => [ + 'content-type' => 'application/json', + 'charset' => 'UTF-8', + 'accept-origin' => '*' + ], + 'access' => [ + 'visibility' => 'protected' + ], + 'providers' => ['context'] +]); + +/** + * @var \equal\php\Context $context + */ +['context' => $context] = $providers; + +$data_folder = "packages/{$params['package']}/init/anonymize"; + +if(file_exists($data_folder) && is_dir($data_folder)) { + // handle JSON files + foreach(glob("$data_folder/*.json") as $json_file) { + if(isset($params['config_file']) && $params['config_file'] !== basename($json_file, '.json')) { + continue; + } + + $data = file_get_contents($json_file); + $classes = json_decode($data, true); + if(!$classes) { + continue; + } + foreach($classes as $class) { + if(!isset($class['name'])) { + continue; + } + + $anonymize_params = [ + 'entity' => $class['name'], + ]; + foreach(['lang', 'fields', 'relations', 'domain'] as $param_key) { + if(isset($class[$param_key])) { + $anonymize_params[$param_key] = $class[$param_key]; + } + } + + eQual::run('do', 'core_model_anonymize', $anonymize_params); + } + } +} + +$context->httpResponse() + ->status(204) + ->send(); diff --git a/packages/core/actions/init/package.php b/packages/core/actions/init/package.php index 947586a95..0b34b4743 100644 --- a/packages/core/actions/init/package.php +++ b/packages/core/actions/init/package.php @@ -1,8 +1,9 @@ - Some Rights Reserved, Cedric Francoys, 2010-2024 - Licensed under GNU GPL 3 license + Some Rights Reserved, eQual framework, 2010-2024 + Original author(s): Cedric FRANCOYS + License: GNU LGPL 3 license */ use equal\db\DBConnector; use equal\fs\FSManipulator as FS; @@ -62,16 +63,45 @@ ] ], 'constants' => ['DEFAULT_LANG', 'DB_HOST', 'DB_PORT', 'DB_NAME', 'DB_USER', 'DB_PASSWORD', 'DB_DBMS'], - 'providers' => ['context', 'orm', 'adapt', 'report'], + 'providers' => ['context', 'orm'], ]); /** - * @var \equal\php\Context $context - * @var \equal\orm\ObjectManager $orm - * @var \equal\data\adapt\DataAdapterProvider $dap - * @var \equal\error\Reporter $reporter + * @var \equal\php\Context $context + * @var \equal\orm\ObjectManager $orm + */ +['context' => $context, 'orm' => $orm] = $providers; + +/** + * Methods + */ + +$importDataFromFolderJsonFiles = function($data_folder) { + foreach(glob("$data_folder/*.json") as $json_file) { + $data = file_get_contents($json_file); + $classes = json_decode($data, true); + if(!$classes) { + continue; + } + foreach($classes as $class) { + $entity = $class['name'] ?? null; + if(!$entity) { + continue; + } + $lang = $class['lang'] ?? constant('DEFAULT_LANG'); + + eQual::run('do', 'core_model_import', [ + 'entity' => $entity, + 'data' => $class['data'], + 'lang' => $lang + ]); + } + } +}; + +/** + * Action */ -['context' => $context, 'orm' => $orm, 'adapt' => $dap, 'report' => $reporter] = $providers; $skip_package = false; @@ -90,9 +120,6 @@ // silently ignore package when dependency of another } else { - // retrieve adapter for converting data from JSON files - $adapter = $dap->get('json'); - // make sure DB is available eQual::run('do', 'test_db-access'); @@ -172,176 +199,19 @@ // 2) Populate tables with predefined data $data_folder = "packages/{$params['package']}/init/data"; if($params['import'] && file_exists($data_folder) && is_dir($data_folder)) { - // handle JSON files - foreach(glob($data_folder."/*.json") as $json_file) { - $data = file_get_contents($json_file); - $classes = json_decode($data, true); - if(!$classes) { - continue; - } - foreach($classes as $class) { - $entity = $class['name'] ?? null; - if(!$entity) { - continue; - } - $lang = $class['lang'] ?? constant('DEFAULT_LANG'); - $model = $orm->getModel($entity); - $schema = $model->getSchema(); - - $objects_ids = []; - - foreach($class['data'] as $odata) { - foreach($odata as $field => $value) { - if(!isset($schema[$field])) { - $reporter->warning("ORM::unknown field {$field} in json file '{$json_file}'."); - continue; - } - $f = new Field($schema[$field]); - $odata[$field] = $adapter->adaptIn($value, $f->getUsage()); - } - - if(isset($odata['id'])) { - $res = $orm->search($entity, ['id', '=', $odata['id']]); - if($res > 0 && count($res)) { - // object already exists (either values or language might differ) - } - else { - $orm->create($entity, ['id' => $odata['id']], $lang, false); - } - $id = $odata['id']; - unset($odata['id']); - } - else { - $id = $orm->create($entity, [], $lang); - } - $orm->update($entity, $id, $odata, $lang); - $objects_ids[] = $id; - } - - // force a first generation of computed fields, if any - $computed_fields = []; - foreach($schema as $field => $def) { - if($def['type'] == 'computed') { - $computed_fields[] = $field; - } - } - $orm->read($entity, $objects_ids, $computed_fields, $lang); - } - } + $importDataFromFolderJsonFiles($data_folder); } // 2 bis) Populate tables with demo data, if requested $demo_folder = "packages/{$params['package']}/init/demo"; if($params['demo'] && file_exists($demo_folder) && is_dir($demo_folder)) { - // handle JSON files - foreach(glob($demo_folder."/*.json") as $json_file) { - $data = file_get_contents($json_file); - $classes = json_decode($data, true); - if(!$classes) { - continue; - } - foreach($classes as $class) { - $entity = $class['name'] ?? null; - if(!$entity) { - continue; - } - $lang = $class['lang'] ?? constant('DEFAULT_LANG'); - $model = $orm->getModel($entity); - $schema = $model->getSchema(); - - $objects_ids = []; - - foreach($class['data'] as $odata) { - foreach($odata as $field => $value) { - $f = new Field($schema[$field]); - $odata[$field] = $adapter->adaptIn($value, $f->getUsage()); - } - - if(isset($odata['id'])) { - $res = $orm->search($entity, ['id', '=', $odata['id']]); - if($res > 0 && count($res)) { - // object already exist, but either values or language might differ - } - else { - $orm->create($entity, ['id' => $odata['id']], $lang, false); - } - $id = $odata['id']; - unset($odata['id']); - } - else { - $id = $orm->create($entity, [], $lang); - } - $orm->update($entity, $id, $odata, $lang); - $objects_ids[] = $id; - } - - // force a first generation of computed fields, if any - $computed_fields = []; - foreach($schema as $field => $def) { - if($def['type'] == 'computed') { - $computed_fields[] = $field; - } - } - $orm->read($entity, $objects_ids, $computed_fields, $lang); - } - } + $importDataFromFolderJsonFiles($demo_folder); } // 2 ter) Populate tables with test data (intended for tests), if requested $test_folder = "packages/{$params['package']}/init/test"; if($params['test'] && file_exists($test_folder) && is_dir($test_folder)) { - // handle JSON files - foreach(glob($test_folder."/*.json") as $json_file) { - $data = file_get_contents($json_file); - $classes = json_decode($data, true); - if(!$classes) { - continue; - } - foreach($classes as $class) { - $entity = $class['name'] ?? null; - if(!$entity) { - continue; - } - $lang = $class['lang'] ?? constant('DEFAULT_LANG'); - $model = $orm->getModel($entity); - $schema = $model->getSchema(); - - $objects_ids = []; - - foreach($class['data'] as $odata) { - foreach($odata as $field => $value) { - $f = new Field($schema[$field]); - $odata[$field] = $adapter->adaptIn($value, $f->getUsage()); - } - - if(isset($odata['id'])) { - $res = $orm->search($entity, ['id', '=', $odata['id']]); - if($res > 0 && count($res)) { - // object already exist, but either values or language might differ - } - else { - $orm->create($entity, ['id' => $odata['id']], $lang, false); - } - $id = $odata['id']; - unset($odata['id']); - } - else { - $id = $orm->create($entity, [], $lang); - } - $orm->update($entity, $id, $odata, $lang); - $objects_ids[] = $id; - } - - // force a first generation of computed fields, if any - $computed_fields = []; - foreach($schema as $field => $def) { - if($def['type'] == 'computed') { - $computed_fields[] = $field; - } - } - $orm->read($entity, $objects_ids, $computed_fields, $lang); - } - } + $importDataFromFolderJsonFiles($test_folder); } // 3) If a `bin` folder exists, copy its content to /bin// diff --git a/packages/core/actions/init/seed.php b/packages/core/actions/init/seed.php new file mode 100644 index 000000000..3ef5a9310 --- /dev/null +++ b/packages/core/actions/init/seed.php @@ -0,0 +1,78 @@ + + Some Rights Reserved, The eQual Framework, 2010-2024 + Author: The eQual Framework Contributors + Original Author: Lucas Laurent + Licensed under GNU LGPL 3 license +*/ + +list($params, $providers) = eQual::announce([ + 'description' => 'Seed objects for package using json configuration files in "{package}/init/seed/".', + 'params' => [ + 'package' => [ + 'description' => 'Name of the package to seed.', + 'type' => 'string', + 'usage' => 'orm/package', + 'required' => true + ], + 'config_file' => [ + 'description' => 'Name of the configuration file to use to seed objects.', + 'help' => 'Configuration file must match the format "{package}/init/seed/{config_file}.json".' + . ' If no config file specified, then all files of seed folder are used.', + 'type' => 'string' + ] + ], + 'response' => [ + 'content-type' => 'application/json', + 'charset' => 'UTF-8', + 'accept-origin' => '*' + ], + 'access' => [ + 'visibility' => 'protected' + ], + 'constants' => ['DEFAULT_LANG'], + 'providers' => ['context'] +]); + +/** + * @var \equal\php\Context $context + */ +['context' => $context] = $providers; + +$data_folder = "packages/{$params['package']}/init/seed"; + +if(file_exists($data_folder) && is_dir($data_folder)) { + // handle JSON files + foreach(glob("$data_folder/*.json") as $json_file) { + if(isset($params['config_file']) && $params['config_file'] !== basename($json_file, '.json')) { + continue; + } + + $data = file_get_contents($json_file); + $classes = json_decode($data, true); + if(!$classes) { + continue; + } + foreach($classes as $class) { + if(!isset($class['name'])) { + continue; + } + + $generate_params = [ + 'entity' => $class['name'], + ]; + foreach(['qty', 'fields', 'relations', 'set_object_data', 'lang'] as $param_key) { + if(isset($class[$param_key])) { + $generate_params[$param_key] = $class[$param_key]; + } + } + + eQual::run('do', 'core_model_generate', $generate_params); + } + } +} + +$context->httpResponse() + ->status(201) + ->send(); diff --git a/packages/core/actions/model/anonymize.php b/packages/core/actions/model/anonymize.php new file mode 100644 index 000000000..c26435d65 --- /dev/null +++ b/packages/core/actions/model/anonymize.php @@ -0,0 +1,136 @@ + + Some Rights Reserved, The eQual Framework, 2010-2024 + Author: The eQual Framework Contributors + Original Author: Lucas Laurent + Licensed under GNU LGPL 3 license +*/ +use equal\data\DataGenerator; + +list($params, $providers) = eQual::announce([ + 'description' => "Anonymize an existing object with random data and given values.", + 'params' => [ + 'entity' => [ + 'description' => 'Full name (including namespace) of the class to return (e.g. \'core\\User\').', + 'type' => 'string', + 'required' => true + ], + 'fields' => [ + 'description' => 'List of fields that must be anonymized.', + 'type' => 'array', + 'default' => [] + ], + 'relations' => [ + 'description' => 'How to handle the object relations (many2one and one2many)', + 'type' => 'array', + 'default' => [] + ], + 'lang' => [ + 'description ' => 'Specific language for multilang field.', + 'type' => 'string', + 'default' => constant('DEFAULT_LANG') + ], + 'domain' => [ + 'description' => 'Criteria that results have to match (series of conjunctions)', + 'type' => 'array', + 'default' => [] + ] + ], + 'response' => [ + 'content-type' => 'application/json', + 'charset' => 'UTF-8', + 'accept-origin' => '*' + ], + 'access' => [ + 'visibility' => 'protected' + ], + 'constants' => ['DEFAULT_LANG'], + 'providers' => ['context', 'orm'] +]); + +/** + * @var \equal\php\Context $context + * @var \equal\orm\ObjectManager $orm + */ +['context' => $context, 'orm' => $orm] = $providers; + +$anonymized_values = []; + +$model = $orm->getModel($params['entity']); +if(!$model) { + throw new Exception("unknown_entity", QN_ERROR_INVALID_PARAM); +} + +$schema = $model->getSchema(); + +$ids = $params['entity']::search($params['domain'])->ids(); +foreach($ids as $id) { + foreach($schema as $field => $descriptor) { + if(!($descriptor['sensitive'] ?? false) && !in_array($field, $params['fields'])) { + continue; + } + + if(isset($descriptor['generation']) && method_exists($params['entity'], $descriptor['generation'])) { + $anonymized_values[$field] = $params['entity']::{$descriptor['generation']}(); + } + else { + $required = $descriptor['required'] ?? false; + if(!$required && DataGenerator::boolean(0.05)) { + $anonymized_values[$field] = null; + } + else { + $anonymized_values[$field] = DataGenerator::generateFromField($field, $descriptor, $params['lang']); + } + } + } + + if(!empty($anonymized_values)) { + $instance = $params['entity']::id($id) + ->update($anonymized_values) + ->read(['id']) + ->first(); + } + + foreach($schema as $field => $descriptor) { + if(!in_array($descriptor['type'], ['one2many', 'many2one']) || !isset($params['relations'][$field])) { + continue; + } + + $relation_params = [ + 'entity' => $descriptor['foreign_object'], + 'lang' => $params['lang'] + ]; + + foreach(['fields', 'relations', 'domain'] as $param_key) { + if(isset($params['relations'][$field][$param_key])) { + $relation_params[$param_key] = $params['relations'][$field][$param_key]; + } + } + + if(!isset($relation_params['domain'])) { + $relation_params['domain'] = []; + } + + switch($descriptor['type']) { + case 'one2many': + $relation_params['domain'][] = [$descriptor['foreign_field'], '=', $id]; + break; + case 'many2one': + $instance = $params['entity']::id($id) + ->read([$field]) + ->first(); + + if(!is_null($instance[$field])) { + $relation_params['domain'][] = ['id', '=', $instance[$field]]; + } + break; + } + + eQual::run('do', 'core_model_anonymize', $relation_params); + } +} + +$context->httpResponse() + ->status(204) + ->send(); diff --git a/packages/core/actions/model/generate.php b/packages/core/actions/model/generate.php new file mode 100644 index 000000000..d9a803ba5 --- /dev/null +++ b/packages/core/actions/model/generate.php @@ -0,0 +1,386 @@ + + Some Rights Reserved, The eQual Framework, 2010-2024 + Author: The eQual Framework Contributors + Original Author: Lucas Laurent + Licensed under GNU LGPL 3 license +*/ +use equal\data\DataGenerator; +use equal\orm\Domain; + +list($params, $providers) = eQual::announce([ + 'description' => "Generate a new object with random data and given values.", + 'params' => [ + 'entity' => [ + 'description' => 'Full name (including namespace) of the class to return (e.g. \'core\\User\').', + 'type' => 'string', + 'required' => true + ], + 'qty' => [ + 'description' => 'The quantity of objects to generate. Supports both integer notation and array notation.', + 'type' => 'string', + 'default' => '1' + ], + 'fields' => [ + 'description' => 'Associative array mapping fields to their related values.', + 'type' => 'array', + 'default' => [] + ], + 'relations' => [ + 'description' => 'Relational fields descriptor, telling how to handle the object relations (many2one, one2many and many2many).', + 'type' => 'array', + 'default' => [] + ], + 'set_object_data' => [ + 'description' => 'Associative array of the generated object data to add to the object.', + 'help' => 'The object is accessible in domain like this "object.{field}"', + 'type' => 'array', + 'default' => [] + ], + 'object_data' => [ + 'description' => 'Current object data (carry).', + 'type' => 'array', + 'default' => [] + ], + 'lang' => [ + 'description ' => 'Specific language for multilang field.', + 'type' => 'string', + 'default' => constant('DEFAULT_LANG') + ] + ], + 'response' => [ + 'content-type' => 'application/json', + 'charset' => 'UTF-8', + 'accept-origin' => '*' + ], + 'access' => [ + 'visibility' => 'protected' + ], + 'constants' => ['DEFAULT_LANG'], + 'providers' => ['context', 'orm'] +]); + +/** + * @var \equal\php\Context $context + * @var \equal\orm\ObjectManager $orm + */ +['context' => $context, 'orm' => $orm] = $providers; + +/** + * Methods + */ + +$getQty = function($qty) { + $result = 1; + if(is_numeric($qty)) { + $result = intval($qty); + } + elseif(!is_array($qty)) { + // handle array notation + $data = json_decode($qty); + if($data && is_array($data) && count($data) === 2) { + $qty = $data; + } + } + + if(is_array($qty) && count($qty) === 2) { + $result = rand($qty[0], $qty[1]); + } + + return $result; +}; + +$getRelationItemsIds = function(string $entity, array $relation_domain, array $object_data) use ($orm) { + $model = $orm->getModel($entity); + if(!$model) { + throw new Exception("unknown_entity", EQ_ERROR_INVALID_PARAM); + } + + $domain = []; + if(!empty($relation_domain)) { + $domain = (new Domain($relation_domain)) + ->parse($object_data) + ->toArray(); + } + + return $entity::search($domain)->ids(); +}; + +$createGenerateParams = function(array $field_descriptor, array $relation_descriptor, array $object_data, string $lang) { + $model_generate_params = [ + 'entity' => $field_descriptor['foreign_object'], + 'lang' => $lang + ]; + foreach(['qty', 'fields', 'relations', 'set_object_data'] as $param_key) { + if(isset($relation_descriptor[$param_key])) { + $model_generate_params[$param_key] = $relation_descriptor[$param_key]; + } + } + + if(!empty($object_data)) { + $model_generate_params['object_data'] = $object_data; + } + + return $model_generate_params; +}; + +$generateMany2One = function(array $field_descriptor, array $relation_descriptor, string $lang, array $object_data) use ($createGenerateParams) { + $model_generate_params = $createGenerateParams($field_descriptor, $relation_descriptor, $object_data, $lang); + return eQual::run('do', 'core_model_generate', $model_generate_params); +}; + +$generateMany2Many = function(array $field_descriptor, array $relation_descriptor, string $lang, array $object_data) use ($createGenerateParams) { + $model_generate_params = $createGenerateParams($field_descriptor, $relation_descriptor, $object_data, $lang); + return eQual::run('do', 'core_model_generate', $model_generate_params); +}; + +$generateOne2Many = function(int $id, array $field_descriptor, array $relation_descriptor, string $lang, array $object_data) use ($createGenerateParams) { + $model_generate_params = $createGenerateParams($field_descriptor, $relation_descriptor, $object_data, $lang); + if(!isset($model_generate_params['fields'])) { + $model_generate_params['fields'] = []; + } + $model_generate_params['fields'][$field_descriptor['foreign_field']] = $id; + return eQual::run('do', 'core_model_generate', $model_generate_params); +}; + +$addRelationResultToObjectData = function(array &$object_data, string $field, array $set_object_data, array $relation_result) { + foreach(array_keys($set_object_data) as $key) { + if(isset($relation_result['object_data'][$key])) { + $object_data["$field.$key"] = $relation_result['object_data'][$key]; + } + } +}; + +$addRelationResultsToObjectData = function(array &$object_data, string $field, array $set_object_data, array $relation_results) { + $i = 0; + foreach($relation_results as $relation_result) { + foreach(array_keys($set_object_data) as $key) { + if(isset($relation_result['object_data'][$key])) { + $object_data["$field.$i.$key"] = $relation_result['object_data'][$key]; + } + } + $i++; + } +}; + +/** + * Action + */ + +$model = $orm->getModel($params['entity']); +if(!$model) { + throw new Exception("unknown_entity", EQ_ERROR_INVALID_PARAM); +} + +$root_fields = ['id', 'creator', 'created', 'modifier', 'modified', 'deleted', 'state']; + +$qty = $getQty($params['qty']); + +// #todo: Handle multi columns unique (only single column unique is handled) +// #todo: Handle unique for many2one relations +$model_unique_conf = []; +if(method_exists($params['entity'], 'getUnique')) { + $model_unique_conf = $model->getUnique(); +} + +$schema = $model->getSchema(); + +$results = []; +for($i = 0; $i < $qty; $i++) { + $new_entity = []; + foreach($schema as $field => $field_descriptor) { + $field_value_forced = isset($params['fields'][$field]); + $field_has_generate_function = isset($field_descriptor['generation']) && method_exists($params['entity'], $field_descriptor['generation']); + + if( !$field_value_forced + && !$field_has_generate_function + && ( in_array($field, $root_fields) || in_array($field_descriptor['type'], ['alias', 'computed', 'one2many', 'many2one', 'many2many']) ) ) { + continue; + } + + $is_required = $field_descriptor['required'] ?? false; + + $should_be_unique = $field_descriptor['unique'] ?? false; + if(!$should_be_unique && !empty($model_unique_conf)) { + foreach($model_unique_conf as $unique_conf) { + if(count($unique_conf) === 1 && $unique_conf[0] === $field) { + $should_be_unique = true; + break; + } + } + } + + $field_value_allowed = false; + $unique_retry_count = 10; + while($unique_retry_count > 0 && !$field_value_allowed) { + $unique_retry_count--; + + if($field_value_forced) { + $new_entity[$field] = $params['fields'][$field]; + } + elseif($field_has_generate_function && method_exists($params['entity'], $field_descriptor['generation'])) { + $new_entity[$field] = $params['entity']::{$field_descriptor['generation']}(); + } + else { + if(!$is_required && DataGenerator::boolean(0.05)) { + $new_entity[$field] = null; + } + else { + $new_entity[$field] = DataGenerator::generateFromField($field, $field_descriptor, $params['lang']); + } + } + + if($should_be_unique) { + $ids = $params['entity']::search([$field, '=', $new_entity[$field]])->ids(); + if(empty($ids)) { + $field_value_allowed = true; + } + elseif($field_value_forced && $is_required) { + trigger_error("PHP::skip creation of {$params['entity']} because of value $new_entity[$field] for $field field.", EQ_REPORT_WARNING); + continue 3; + } + } + else { + $field_value_allowed = true; + } + } + + if(!$field_value_allowed) { + if($is_required) { + trigger_error("PHP::skip creation of {$params['entity']} because not able to generate a unique value for $field field.", EQ_REPORT_WARNING); + continue 2; + } + else { + unset($new_entity[$field]); + } + } + } + + foreach($params['relations'] as $field => $relation_descriptor) { + $field_descriptor = $schema[$field] ?? null; + $field_type = $field_descriptor['result_type'] ?? $field_descriptor['type'] ?? null; + if(is_null($field_descriptor) || !in_array($field_type, ['many2one', 'many2many'])) { + continue; + } + + $is_required = $field_descriptor['required'] ?? false; + + $ids = []; + $mode = $relation_descriptor['mode'] ?? 'use-existing-or-create'; + if($mode !== 'create') { + $ids = $getRelationItemsIds( + $field_descriptor['foreign_object'], + $relation_descriptor['domain'] ?? [], + array_merge($new_entity, $params['object_data']) + ); + if($mode === 'use-existing-or-create') { + $mode = empty($ids) || DataGenerator::boolean() ? 'create' : 'use-existing'; + } + elseif($mode === 'use-existing' && empty($ids)) { + $mode = 'create'; + trigger_error("PHP::cannot use 'use-existing' for {$field_descriptor['foreign_object']} relation of {$params['entity']} because nothing to link, fallback on 'create'.", EQ_REPORT_WARNING); + } + } + + switch($field_type) { + case 'many2one': + switch($mode) { + case 'use-existing': + if(!$is_required) { + $ids[] = null; + } + + $new_entity[$field] = $ids[array_rand($ids)]; + break; + case 'create': + $relation_results = $generateMany2One($field_descriptor, $relation_descriptor, $params['lang'], $params['object_data']); + if(count($relation_results) === 1) { + $new_entity[$field] = $relation_results[0]['id']; + + if(!empty($relation_descriptor['set_object_data'])) { + $addRelationResultToObjectData($params['object_data'], $field, $relation_descriptor['set_object_data'], $relation_results[0]); + } + } + elseif($is_required) { + trigger_error("PHP::skip creation of {$params['entity']} because not able to generate object for $field field.", EQ_REPORT_WARNING); + continue 4; + } + break; + } + break; + case 'many2many': + switch($mode) { + case 'use-existing': + $relation_qty = $getQty($relation_descriptor['qty']); + + $random_ids = []; + for($j = 0; $j < $relation_qty; $j++) { + if(empty($ids)) { + break; + } + + $random_index = array_rand($ids); + $random_ids[] = $ids[$random_index]; + array_splice($ids, $random_index, 1); + } + + if(!empty($random_ids)) { + $new_entity[$field] = $random_ids; + } + break; + case 'create': + $relation_results = $generateMany2Many($field_descriptor, $relation_descriptor, $params['lang'], $params['object_data']); + + $new_relation_entities_ids = array_column($relation_results, 'id'); + if(!empty($new_relation_entities_ids)) { + $new_entity[$field] = $new_relation_entities_ids; + + if(!empty($relation_descriptor['set_object_data'])) { + $addRelationResultsToObjectData($params['object_data'], $field, $relation_descriptor['set_object_data'], $relation_results); + } + } + break; + } + break; + } + } + + $field_to_read = array_values($params['set_object_data']); + + $instance = $params['entity']::create($new_entity, $params['lang']) + ->read($field_to_read) + ->adapt('json') + ->first(true); + + foreach($params['set_object_data'] as $key => $field) { + if(isset($instance[$field])) { + $params['object_data'][$key] = $instance[$field]; + } + } + + foreach($params['relations'] as $field => $relation_descriptor) { + $field_descriptor = $schema[$field] ?? null; + $field_type = $field_descriptor['result_type'] ?? $field_descriptor['type'] ?? null; + if(is_null($field_descriptor) || $field_type !== 'one2many') { + continue; + } + + $relation_results = $generateOne2Many($instance['id'], $field_descriptor, $relation_descriptor, $params['lang'], $params['object_data']); + + if(!empty($relation_descriptor['set_object_data']) && !empty($relation_results)) { + $addRelationResultsToObjectData($params['object_data'], $field, $relation_descriptor['set_object_data'], $relation_results); + } + } + + $results[] = [ + 'entity' => $params['entity'], + 'id' => $instance['id'], + 'object_data' => $params['object_data'] + ]; +} + +$context->httpResponse() + ->status(201) + ->body($results) + ->send(); diff --git a/packages/core/actions/model/import-csv-file.php b/packages/core/actions/model/import-csv-file.php new file mode 100644 index 000000000..0cb48543b --- /dev/null +++ b/packages/core/actions/model/import-csv-file.php @@ -0,0 +1,84 @@ + + Some Rights Reserved, eQual framework, 2010-2024 + Original author(s): Lucas LAURENT + License: GNU LGPL 3 license +*/ + +use core\import\EntityMapping; + +[$params, $providers] = eQual::announce([ + 'description' => 'Import eQual database data from a csv external source.', + 'params' => [ + 'entity_mapping_id' => [ + 'type' => 'many2one', + 'foreign_object' => 'core\import\EntityMapping', + 'description' => 'The entity mapping to use to adapt given data to eQual entities.', + 'required' => true, + 'domain' => ['mapping_type', '=', 'index'] + ], + 'csv_separator_character' => [ + 'type' => 'string', + 'default' => ',', + 'min' => 1, + 'max' => 1 + ], + 'csv_enclosure_character' => [ + 'type' => 'string', + 'default' => '"', + 'min' => 1, + 'max' => 1 + ], + 'csv_escape_character' => [ + 'type' => 'string', + 'default' => '\\', + 'min' => 1, + 'max' => 1 + ], + 'data' => [ + 'type' => 'binary', + 'description' => 'Payload of the file (raw file data).', + 'required' => true + ] + ], + 'response' => [ + 'content-type' => 'application/json', + 'charset' => 'utf-8', + 'accept-origin' => '*' + ], + 'constants' => ['UPLOAD_MAX_FILE_SIZE'], + 'providers' => ['context'] +]); + +['context' => $context] = $providers; + +$entity_mapping = EntityMapping::id($params['entity_mapping_id']) + ->read(['id']) + ->first(); + +if(is_null($entity_mapping)) { + throw new Exception('unknown_entity_mapping', EQ_ERROR_UNKNOWN_OBJECT); +} + +if(strlen($params['data']) > constant('UPLOAD_MAX_FILE_SIZE')) { + throw new Exception('maximum_size_exceeded', EQ_ERROR_INVALID_PARAM); +} + +$data = []; + +$lines = explode(PHP_EOL, $params['data']); +foreach($lines as $line) { + if(!empty($line)) { + $data[] = str_getcsv($line, $params['csv_separator_character'], $params['csv_enclosure_character'], $params['csv_escape_character']); + } +} + +eQual::run('do', 'core_model_import-mapped', [ + 'entity_mapping_id' => $entity_mapping['id'], + 'data' => $data +]); + +$context->httpResponse() + ->status(204) + ->send(); diff --git a/packages/core/actions/model/import-json-file.php b/packages/core/actions/model/import-json-file.php new file mode 100644 index 000000000..b25f7c8c9 --- /dev/null +++ b/packages/core/actions/model/import-json-file.php @@ -0,0 +1,61 @@ + + Some Rights Reserved, eQual framework, 2010-2024 + Original author(s): Lucas LAURENT + License: GNU LGPL 3 license +*/ + +use core\import\EntityMapping; + +[$params, $providers] = eQual::announce([ + 'description' => 'Import eQual database data from a json external source.', + 'params' => [ + 'entity_mapping_id' => [ + 'type' => 'many2one', + 'foreign_object' => 'core\import\EntityMapping', + 'description' => 'The entity mapping to use to adapt given data to eQual entities.', + 'required' => true, + 'domain' => ['mapping_type', '=', 'name'] + ], + 'data' => [ + 'type' => 'binary', + 'description' => 'Payload of the file (raw file data).', + 'required' => true + ] + ], + 'response' => [ + 'content-type' => 'application/json', + 'charset' => 'utf-8', + 'accept-origin' => '*' + ], + 'constants' => ['UPLOAD_MAX_FILE_SIZE'], + 'providers' => ['context'] +]); + +['context' => $context] = $providers; + +$entity_mapping = EntityMapping::id($params['entity_mapping_id']) + ->read(['id']) + ->first(); + +if(is_null($entity_mapping)) { + throw new Exception('unknown_entity_mapping', EQ_ERROR_UNKNOWN_OBJECT); +} + +if(empty($params['data'])) { + throw new Exception('empty_data', EQ_ERROR_INVALID_PARAM); +} + +if(strlen($params['data']) > constant('UPLOAD_MAX_FILE_SIZE')) { + throw new Exception('maximum_size_exceeded', EQ_ERROR_INVALID_PARAM); +} + +eQual::run('do', 'core_model_import-mapped', [ + 'entity_mapping_id' => $entity_mapping['id'], + 'data' => json_decode($params['data'], true) +]); + +$context->httpResponse() + ->status(204) + ->send(); diff --git a/packages/core/actions/model/import-json.php b/packages/core/actions/model/import-json.php new file mode 100644 index 000000000..aa0d2b867 --- /dev/null +++ b/packages/core/actions/model/import-json.php @@ -0,0 +1,56 @@ + + Some Rights Reserved, eQual framework, 2010-2024 + Original author(s): Lucas LAURENT + License: GNU LGPL 3 license +*/ + +use core\import\EntityMapping; + +[$params, $providers] = eQual::announce([ + 'description' => 'Import eQual database data from a json external source.', + 'params' => [ + 'entity_mapping_id' => [ + 'type' => 'many2one', + 'foreign_object' => 'core\import\EntityMapping', + 'description' => 'The entity mapping to use to adapt given data to eQual entities.', + 'required' => true, + 'domain' => ['mapping_type', '=', 'name'] + ], + 'data' => [ + 'type' => 'string', + 'description' => 'Text JSON payload.', + 'required' => true + ] + ], + 'response' => [ + 'content-type' => 'application/json', + 'charset' => 'utf-8', + 'accept-origin' => '*' + ], + 'providers' => ['context'] +]); + +['context' => $context] = $providers; + +$entity_mapping = EntityMapping::id($params['entity_mapping_id']) + ->read(['id']) + ->first(); + +if(is_null($entity_mapping)) { + throw new Exception('unknown_entity_mapping', EQ_ERROR_UNKNOWN_OBJECT); +} + +if(empty($params['data'])) { + throw new Exception('empty_data', EQ_ERROR_INVALID_PARAM); +} + +eQual::run('do', 'core_model_import-mapped', [ + 'entity_mapping_id' => $entity_mapping['id'], + 'data' => json_decode($params['data'], true) +]); + +$context->httpResponse() + ->status(204) + ->send(); diff --git a/packages/core/actions/model/import-mapped.php b/packages/core/actions/model/import-mapped.php new file mode 100644 index 000000000..b7db0783f --- /dev/null +++ b/packages/core/actions/model/import-mapped.php @@ -0,0 +1,204 @@ + + Some Rights Reserved, eQual framework, 2010-2024 + Original author(s): Lucas LAURENT + License: GNU LGPL 3 license +*/ + +use core\import\DataTransformer; +use core\import\EntityMapping; +use equal\data\adapt\DataAdapterProvider; +use equal\orm\ObjectManager; +use equal\php\Context; + +[$params, $providers] = eQual::announce([ + 'description' => 'Import eQual model data from external source using EntityMapping.', + 'params' => [ + 'entity_mapping_id' => [ + 'type' => 'many2one', + 'foreign_object' => 'core\import\EntityMapping', + 'description' => 'The entity mapping to use to adapt given data to eQual entities.', + 'required' => true + ], + 'data' => [ + 'type' => 'array', + 'description' => 'The data that needs to be imported.', + 'required' => true + ], + 'indexation' => [ + 'type' => 'array', + 'description' => 'List of field keys that describe the indexation of origin data rows.', + 'help' => 'Must be a list of strings. If empty the mapping is done on array index or key of associative array.' + ] + ], + 'response' => [ + 'content-type' => 'application/json', + 'charset' => 'utf-8', + 'accept-origin' => '*' + ], + 'providers' => ['context', 'orm', 'adapt'] +]); + +/** + * @var Context $context + * @var ObjectManager $orm + * @var DataAdapterProvider $dap + */ +['context' => $context, 'orm' => $orm, 'adapt' => $dap] = $providers; + +$adapter = $dap->get('json'); + +/** + * Methods + */ + +$createMapColMapping = function(array $entity_mapping, array $data, array $indexation) { + $map_col_mapping = []; + + switch($entity_mapping['mapping_type']) { + case 'index': + foreach(array_keys($data[0]) as $index) { + $col_mapping_found = false; + foreach($entity_mapping['column_mappings_ids'] as $col_mapping) { + if($col_mapping['origin_index'] === $index) { + $map_col_mapping[] = $col_mapping; + $col_mapping_found = true; + break; + } + } + if(!$col_mapping_found) { + $map_col_mapping[] = null; + } + } + break; + case 'name': + if(!empty($indexation)) { + $not_string_keys = array_filter( + $indexation, + function($key) { + return !is_string($key); + } + ); + + if(!empty($not_string_keys)) { + throw new Exception('indexation_must_be_an_array_of_strings', EQ_ERROR_INVALID_PARAM); + } + + foreach($indexation as $column_key) { + foreach($entity_mapping['column_mappings_ids'] as $col_mapping) { + if($col_mapping['origin_name'] === $column_key) { + $map_col_mapping[] = $col_mapping; + } + } + } + } + else { + foreach(array_keys($data[0]) as $column_key) { + foreach($entity_mapping['column_mappings_ids'] as $col_mapping) { + if($col_mapping['origin_name'] === $column_key) { + $map_col_mapping[$column_key] = $col_mapping; + } + } + } + } + break; + } + + foreach($map_col_mapping as $col_mapping) { + usort($col_mapping['data_transformers_ids'], 'order'); + } + + return $map_col_mapping; +}; + +/** + * Action + */ + +if(empty($params['data'][0])) { + throw new Exception('empty_data', EQ_ERROR_INVALID_PARAM); +} + +$entity_mapping = EntityMapping::id($params['entity_mapping_id']) + ->read([ + 'entity', + 'mapping_type', + 'column_mappings_ids' => [ + 'origin_name', + 'origin_index', + 'target_name', + 'data_transformers_ids' => [ + 'transformer_type', + 'transformer_subtype', + 'order', + 'value', + 'cast_to', + 'transform_by', + 'field_contains_value', + 'map_values_ids' => ['old_value', 'new_value'] + ] + ] + ]) + ->first(true); + +if(is_null($entity_mapping)) { + throw new Exception('unknown_entity_mapping', EQ_ERROR_UNKNOWN_OBJECT); +} + +$model = $orm->getModel($entity_mapping['entity']); +if(!$model) { + throw new Exception('unknown_entity', QN_ERROR_INVALID_PARAM); +} + +if(empty($entity_mapping['column_mappings_ids'])) { + throw new Exception('no_column_mappings', EQ_ERROR_INVALID_PARAM); +} + +$map_col_mapping = $createMapColMapping( + $entity_mapping, + $params['data'], + $params['indexation'] ?? [] +); + +$entities_data = []; +foreach($params['data'] as $row_index => $row) { + $entity_data = []; + foreach($row as $column_index => $column_data) { + $column_mapping = $map_col_mapping[$column_index] ?? null; + if(is_null($column_mapping)) { + continue; + } + + $f = $model->getField($column_mapping['target_name']); + if(is_null($f)) { + continue; + } + + $value = $adapter->adaptIn($column_data, $f->getUsage()); + + foreach($column_mapping['data_transformers_ids'] as $data_transformer) { + $value = DataTransformer::transformValue($data_transformer, $value); + } + + $entity_data[$column_mapping['target_name']] = $value; + } + + if(!empty($entity_data)) { + $entities_data[] = $entity_data; + } + else { + trigger_error("PHP::model not imported for index $row_index", EQ_REPORT_WARNING); + } +} + +if(!empty($entities_data)) { + eQual::run('do', 'core_model_import', [ + 'entity' => $entity_mapping['entity'], + 'data' => $entities_data + ]); +} + +$context->httpResponse() + ->status(204) + ->send(); diff --git a/packages/core/actions/model/import-old.php b/packages/core/actions/model/import-old.php new file mode 100644 index 000000000..596d4071c --- /dev/null +++ b/packages/core/actions/model/import-old.php @@ -0,0 +1,133 @@ + + Some Rights Reserved, Cedric Francoys, 2010-2021 + Licensed under GNU LGPL 3 license +*/ +use equal\orm\Field; + +list($params, $providers) = announce([ + 'description' => "mark the given object(s) as archived.", + 'params' => [ + 'json' => [ + 'description' => 'Unique identifier of the object to remove.', + 'type' => 'text', + 'default' => '' + ] + ], + 'constants' => ['UPLOAD_MAX_FILE_SIZE'], + 'response' => [ + 'content-type' => 'application/json', + 'charset' => 'utf-8' + ], + 'access' => [ + 'visibility' => 'private' + ], + 'providers' => ['context', 'orm', 'access', 'adapt'] +]); + +/** + * @var \equal\php\Context $context + * @var \equal\orm\ObjectManager $orm + * @var \equal\access\AccessController $ac + * @var \equal\data\DataAdapterProvider $dap + */ +list($context, $orm, $ac, $dap) = [$providers['context'], $providers['orm'], $providers['access'], $providers['adapt']]; + +/** + * This script is meant to be run using CLI, in conjunction with -i arg. + * In order to prevent the context service from consuming data from stdin. + * Example: ./equal.run -i --do=model_import 0) { + $bytes = 0; + $chunk_size = 1024; + $json = ''; + while($s = fgets($stdin, $chunk_size)) { + $bytes += $chunk_size; + if($bytes > constant('UPLOAD_MAX_FILE_SIZE')) { + throw new Exception('max_size_exceeded', QN_ERROR_INVALID_PARAM); + } + $json .= $s; + } + } +} + +$data = []; + +if(strlen($json)) { + $data = json_decode($json, true, 512, JSON_BIGINT_AS_STRING); + if(is_null($data)) { + throw new Exception('invalid json: '.json_last_error_msg(), QN_ERROR_INVALID_PARAM); + } +} + +if(empty($data)) { + throw new Exception('missing_data', QN_ERROR_INVALID_PARAM); +} + +/** @var \equal\data\adapt\DataAdapter */ +$adapter = $dap->get('json'); + +foreach($data as $class) { + + if(!isset($class['name']) || !isset($class['data'])) { + throw new Exception('invalid_schema', QN_ERROR_INVALID_PARAM); + } + + $entity = $class['name']; + $lang = $class['lang']; + $model = $orm->getModel($entity); + $schema = $model->getSchema(); + + $objects_ids = []; + + foreach($class['data'] as $odata) { + foreach($odata as $field => $value) { + /** @var equal\orm\Field */ + $f = $model->getField($field); + $odata[$field] = $adapter->adaptIn($value, $f->getUsage()); + } + if(isset($odata['id'])) { + $res = $orm->search($entity, ['id', '=', $odata['id']]); + if($res > 0 && count($res)) { + // object already exist, but either values or language differs + $id = $odata['id']; + $res = $orm->update($entity, $id, $odata, $lang); + $objects_ids[] = $id; + } + else { + $objects_ids[] = $orm->create($entity, $odata, $lang); + } + } + else { + $objects_ids[] = $orm->create($entity, $odata, $lang); + } + } + + // force a first generation of computed fields, if any + $computed_fields = []; + foreach($schema as $field => $def) { + if($def['type'] == 'computed') { + $computed_fields[] = $field; + } + } + $orm->read($entity, $objects_ids, $computed_fields, $lang); +} diff --git a/packages/core/actions/model/import.php b/packages/core/actions/model/import.php index 596d4071c..274c74cd2 100644 --- a/packages/core/actions/model/import.php +++ b/packages/core/actions/model/import.php @@ -1,133 +1,153 @@ - Some Rights Reserved, Cedric Francoys, 2010-2021 - Licensed under GNU LGPL 3 license + This file is part of the eQual framework + Some Rights Reserved, eQual framework, 2010-2024 + Original author(s): Lucas LAURENT + License: GNU LGPL 3 license */ + use equal\orm\Field; +use equal\orm\ObjectManager; +use equal\php\Context; +use equal\data\adapt\DataAdapterProvider; +use equal\error\Reporter; -list($params, $providers) = announce([ - 'description' => "mark the given object(s) as archived.", +[$params, $providers] = eQual::announce([ + 'description' => 'Import eQual object from import format data.', 'params' => [ - 'json' => [ - 'description' => 'Unique identifier of the object to remove.', - 'type' => 'text', - 'default' => '' + 'entity' => [ + 'type' => 'string', + 'usage' => 'orm/entity', + 'description' => 'Full name (including namespace) of the class to import (e.g. "core\\User").' + ], + 'data' => [ + 'type' => 'array', + 'description' => 'List of objects to import.', + 'help' => 'If the entity parameter is not provided, this parameter must match eQual export format (e.g. "{\"entity\":\"core\\\\User\",\"data\":[...]}").', + 'required' => true + ], + 'lang' => [ + 'type' => 'string', + 'description ' => 'Specific language for multilang field.', + 'help' => 'If not provided the DEFAULT_LANG is used.' ] ], - 'constants' => ['UPLOAD_MAX_FILE_SIZE'], 'response' => [ 'content-type' => 'application/json', - 'charset' => 'utf-8' - ], - 'access' => [ - 'visibility' => 'private' + 'charset' => 'utf-8', + 'accept-origin' => '*' ], - 'providers' => ['context', 'orm', 'access', 'adapt'] + 'constants' => ['DEFAULT_LANG'], + 'providers' => ['context', 'orm', 'adapt', 'report'] ]); /** - * @var \equal\php\Context $context - * @var \equal\orm\ObjectManager $orm - * @var \equal\access\AccessController $ac - * @var \equal\data\DataAdapterProvider $dap + * @var Context $context + * @var ObjectManager $orm + * @var DataAdapterProvider $dap + * @var Reporter $reporter */ -list($context, $orm, $ac, $dap) = [$providers['context'], $providers['orm'], $providers['access'], $providers['adapt']]; +['context' => $context, 'orm' => $orm, 'adapt' => $dap, 'report' => $reporter] = $providers; + +$adapter = $dap->get('json'); /** - * This script is meant to be run using CLI, in conjunction with -i arg. - * In order to prevent the context service from consuming data from stdin. - * Example: ./equal.run -i --do=model_import 0) { - $bytes = 0; - $chunk_size = 1024; - $json = ''; - while($s = fgets($stdin, $chunk_size)) { - $bytes += $chunk_size; - if($bytes > constant('UPLOAD_MAX_FILE_SIZE')) { - throw new Exception('max_size_exceeded', QN_ERROR_INVALID_PARAM); - } - $json .= $s; - } + $model = $orm->getModel($entity); + if(!$model) { + throw new Exception('unknown_entity', QN_ERROR_INVALID_PARAM); } -} -$data = []; + return [$entity, $model->getSchema()]; +}; -if(strlen($json)) { - $data = json_decode($json, true, 512, JSON_BIGINT_AS_STRING); - if(is_null($data)) { - throw new Exception('invalid json: '.json_last_error_msg(), QN_ERROR_INVALID_PARAM); +$extractData = function(array $params) { + if(isset($params['entity'])) { + $data = $params['data']; + } + else { + $data = $params['data']['data'] ?? null; } -} - -if(empty($data)) { - throw new Exception('missing_data', QN_ERROR_INVALID_PARAM); -} -/** @var \equal\data\adapt\DataAdapter */ -$adapter = $dap->get('json'); + if(is_null($data)) { + throw new Exception('missing_data', EQ_ERROR_INVALID_PARAM); + } -foreach($data as $class) { + return $data; +}; - if(!isset($class['name']) || !isset($class['data'])) { - throw new Exception('invalid_schema', QN_ERROR_INVALID_PARAM); +$extractLang = function($params) { + if(isset($params['entity'])) { + return $params['lang'] ?? constant('DEFAULT_LANG'); } - $entity = $class['name']; - $lang = $class['lang']; - $model = $orm->getModel($entity); - $schema = $model->getSchema(); + return $params['data']['lang'] ?? constant('DEFAULT_LANG'); +}; - $objects_ids = []; +/** + * Action + */ - foreach($class['data'] as $odata) { - foreach($odata as $field => $value) { - /** @var equal\orm\Field */ - $f = $model->getField($field); - $odata[$field] = $adapter->adaptIn($value, $f->getUsage()); - } - if(isset($odata['id'])) { - $res = $orm->search($entity, ['id', '=', $odata['id']]); - if($res > 0 && count($res)) { - // object already exist, but either values or language differs - $id = $odata['id']; - $res = $orm->update($entity, $id, $odata, $lang); - $objects_ids[] = $id; - } - else { - $objects_ids[] = $orm->create($entity, $odata, $lang); - } - } - else { - $objects_ids[] = $orm->create($entity, $odata, $lang); +[$entity, $schema] = $extractEntityAndSchema($params); +$data = $extractData($params); +$lang = $extractLang($params); + +$object_ids = []; +foreach($data as $entity_data) { + foreach($entity_data as $field => $value) { + if(!isset($schema[$field])) { + $reporter->warning("ORM::unknown field $field for entity $entity in given data."); + continue; } + $f = new Field($schema[$field]); + $entity_data[$field] = $adapter->adaptIn($value, $f->getUsage()); } - // force a first generation of computed fields, if any - $computed_fields = []; - foreach($schema as $field => $def) { - if($def['type'] == 'computed') { - $computed_fields[] = $field; + if(isset($entity_data['id'])) { + $ids = $orm->search($entity, ['id', '=', $entity_data['id']]); + if($ids < 0 || !count($ids)) { + $orm->create($entity, ['id' => $entity_data['id']], $lang, false); } + + $id = $entity_data['id']; + unset($entity_data['id']); + } + else { + $id = $orm->create($entity, [], $lang); } - $orm->read($entity, $objects_ids, $computed_fields, $lang); + + $orm->update($entity, $id, $entity_data, $lang); + + $object_ids[] = $id; +} + +$computed_fields = []; +foreach($schema as $field => $def) { + if($def['type'] === 'computed') { + $computed_fields[] = $field; + } +} + +if(!empty($computed_fields)) { + $orm->read($entity, $object_ids, $computed_fields, $lang); } + +$result = array_map( + function($id) { + return ['id' => $id]; + }, + $object_ids +); + +$context->httpResponse() + ->body($result) + ->status(200) + ->send(); diff --git a/packages/core/apps/app/version b/packages/core/apps/app/version index 5c2ec90ca..0560e438d 100644 --- a/packages/core/apps/app/version +++ b/packages/core/apps/app/version @@ -1 +1 @@ -4c2eb0b70677e149774fd5e3008f1edf +1e04a408428f762c33e905e7ac4ea188 diff --git a/packages/core/apps/app/web.app b/packages/core/apps/app/web.app index 8586fa331..2f3e76f03 100644 Binary files a/packages/core/apps/app/web.app and b/packages/core/apps/app/web.app differ diff --git a/packages/core/classes/Mail.class.php b/packages/core/classes/Mail.class.php index 21c57d8ee..1032d0af5 100644 --- a/packages/core/classes/Mail.class.php +++ b/packages/core/classes/Mail.class.php @@ -154,12 +154,14 @@ public static function send(Email $email, string $object_class = '', int $object if($mailer->send($envelope) == 0) { throw new \Exception('failed_sending_email', EQ_ERROR_UNKNOWN); } + trigger_error("PHP::Mail::send() successfully sent email message {$mail['id']}", EQ_REPORT_INFO); // update the core\Mail object status self::id($mail['id'])->update(['status' => 'sent', 'response_status' => 250]); } catch(\Exception $e) { + trigger_error("PHP::Mail::send() failed: ".$e->getMessage(), EQ_REPORT_ERROR); self::id($mail['id'])->update(['status' => 'failing', 'response_status' => 500, 'response' => $e->getMessage()]); - throw new \Exception($e->getMessage(), $e->getCode()); + throw new \Exception($e->getMessage(), EQ_ERROR_UNKNOWN); } return $mail['id']; @@ -237,6 +239,8 @@ public static function flush() { throw new \Exception('failed_sending_email', EQ_ERROR_UNKNOWN); } + trigger_error("APP::Mail::send() successfully sent email message {$message['id']}", EQ_REPORT_INFO); + // upon successful sending, remove the mail from the outbox $filename = self::MESSAGE_FOLDER.'/'.$file; unlink($filename); @@ -252,6 +256,7 @@ public static function flush() { } catch(\Exception $e) { // sending failed + trigger_error("APP::Mail::flush() failed: ".$e->getMessage(), EQ_REPORT_ERROR); // if the message is linked to a core\Mail object, update the latter's status if(isset($message['id'])) { self::id($message['id'])->update(['status' => 'failing', 'response_status' => 500, 'response' => $e->getMessage()]); @@ -411,7 +416,7 @@ function ($matches) use (&$envelope) { } } catch(\Exception $e) { - trigger_error("ORM::createEnvelope: ".$e->getMessage(), QN_REPORT_ERROR); + trigger_error("ORM::createEnvelope: ".$e->getMessage(), EQ_REPORT_ERROR); } return $envelope; diff --git a/packages/core/classes/User.class.php b/packages/core/classes/User.class.php index 4cc235f3e..fff04181c 100644 --- a/packages/core/classes/User.class.php +++ b/packages/core/classes/User.class.php @@ -39,13 +39,13 @@ public static function getColumns() { 'usage' => 'email', 'required' => true, 'unique' => true, - 'dependencies' => ['name'] + 'dependents' => ['name'] ], 'username' => [ 'type' => 'string', 'unique' => true, - 'dependencies' => ['name'] + 'dependents' => ['name'] ], 'password' => [ @@ -62,18 +62,20 @@ public static function getColumns() { 'firstname' => [ 'type' => 'string', - 'dependencies' => ['fullname', 'name'] + 'dependents' => ['fullname', 'name'] ], 'lastname' => [ 'type' => 'string', - 'dependencies' => ['fullname', 'name'] + 'dependents' => ['fullname', 'name'], + 'sensitive' => true ], 'fullname' => [ 'type' => 'computed', 'result_type' => 'string', - 'function' => 'calcFullname' + 'function' => 'calcFullname', + 'sensitive' => true ], // #todo - add a distinct field for 'locale' diff --git a/packages/core/classes/import/ColumnMapping.class.php b/packages/core/classes/import/ColumnMapping.class.php new file mode 100644 index 000000000..bd40c7693 --- /dev/null +++ b/packages/core/classes/import/ColumnMapping.class.php @@ -0,0 +1,94 @@ + + Some Rights Reserved, Cedric Francoys, 2010-2021 + Licensed under GNU GPL 3 license +*/ +namespace core\import; + +use equal\orm\Model; + +class ColumnMapping extends Model { + + public static function getDescription(): string { + return 'Mapping configuration that allows to import a specific column of an entity.'; + } + + public static function getColumns(): array { + return [ + + 'entity_mapping_id' => [ + 'type' => 'many2one', + 'foreign_object' => 'core\import\EntityMapping', + 'description' => 'The parent EntityMapping this ColumnMapping is part of.', + 'required' => true + ], + + 'mapping_type' => [ + 'type' => 'computed', + 'result_type' => 'string', + 'description' => 'Is the column data mapped by index or by name.', + 'store' => true, + 'instant' => true, + 'function' => 'calcMappingType' + ], + + 'origin_name' => [ + 'type' => 'string', + 'description' => 'Name of the column where the data is to be found.', + 'visible' => ['mapping_type', '=', 'name'] + ], + + 'origin_index' => [ + 'type' => 'integer', + 'usage' => 'number/integer{0,255}', + 'description' => 'Index of the column where the data is to be found.', + 'visible' => ['mapping_type', '=', 'index'] + ], + + 'origin' => [ + 'type' => 'computed', + 'result_type' => 'string', + 'description' => 'Index or name of the column where the data is to be found.', + 'store' => false, + 'function' => 'calcOrigin' + ], + + 'target_name' => [ + 'type' => 'string', + 'description' => 'Name of the target entity\'s column.', + 'required' => true + ], + + 'data_transformers_ids' => [ + 'type' => 'one2many', + 'foreign_object' => 'core\import\DataTransformer', + 'foreign_field' => 'column_mapping_id', + 'description' => 'Mapping configurations defining how to import data to eQual entity.' + ] + + ]; + } + + public static function calcMappingType($self): array { + $result = []; + $self->read(['entity_mapping_id' => ['mapping_type']]); + + foreach($self as $id => $column_mapping) { + $result[$id] = $column_mapping['entity_mapping_id']['mapping_type']; + } + + return $result; + } + + public static function calcOrigin($self): array { + $result = []; + $self->read(['mapping_type', 'origin_name', 'origin_index']); + + foreach($self as $id => $column_mapping) { + $result[$id] = $column_mapping['mapping_type'] === 'index' ? $column_mapping['origin_index'] : $column_mapping['origin_name']; + } + + return $result; + } +} diff --git a/packages/core/classes/import/DataTransformer.class.php b/packages/core/classes/import/DataTransformer.class.php new file mode 100644 index 000000000..f7ce0452e --- /dev/null +++ b/packages/core/classes/import/DataTransformer.class.php @@ -0,0 +1,140 @@ + + Some Rights Reserved, Cedric Francoys, 2010-2021 + Licensed under GNU GPL 3 license +*/ +namespace core\import; + +use equal\orm\Model; + +class DataTransformer extends Model { + + public static function getDescription(): string { + return 'Transform step to adapt external data to eQual entity.'; + } + + public static function getColumns(): array { + return [ + + 'column_mapping_id' => [ + 'type' => 'many2one', + 'foreign_object' => 'core\import\ColumnMapping', + 'description' => 'The parent ColumnMapping this DataTransformer is part of.', + 'required' => true + ], + + 'transformer_type' => [ + 'type' => 'string', + 'selection' => [ + 'value' => 'Value', + 'cast' => 'Cast', + 'round' => 'Round', + 'multiply' => 'Multiply', + 'divide' => 'Divide', + 'field-contains' => 'Field contains', + 'field-does-not-contain' => 'Field does not contain', + 'map-value' => 'Map value' + ], + 'description' => 'The type of transform operation to be applied on a column data to import.', + 'default' => 'value' + ], + + 'order' => [ + 'type' => 'integer', + 'description' => 'For sorting the moments within a day.', + 'default' => 1 + ], + + 'value' => [ + 'type' => 'string', + 'description' => 'A static value to set', + 'visible' => ['transformer_type', '=', 'value'] + ], + + 'cast_to' => [ + 'type' => 'string', + 'selection' => [ + 'string' => 'String', + 'integer' => 'Integer', + 'float' => 'Float', + 'boolean' => 'Boolean' + ], + 'visible' => ['transformer_type', '=', 'cast'] + ], + + 'transform_by' => [ + 'type' => 'integer', + 'description' => 'Value to multiply or divide by.', + 'visible' => ['transformer_type', 'in', ['multiply', 'divide']] + ], + + 'field_contains_value' => [ + 'type' => 'string', + 'description' => 'Value that must be found or not.', + 'visible' => ['transformer_type', 'in', ['field-contains', 'field-does-not-contain']] + ], + + 'map_values_ids' => [ + 'type' => 'one2many', + 'foreign_object' => 'core\import\DataTransformerMapValue', + 'foreign_field' => 'data_transformer_id', + 'description' => 'List a map values to replace a value by another.', + 'visible' => ['transformer_type', '=', 'map-value'] + ] + + ]; + } + + public static function transformValue(array $data_transformer, $value) { + switch($data_transformer['transformer_type']) { + case 'value': + $value = $data_transformer['value']; + break; + case 'cast': + switch($data_transformer['cast_to']) { + case 'string': + $value = (string) $value; + break; + case 'integer': + $value = (int) $value; + break; + case 'float': + $value = (float) $value; + break; + case 'boolean': + $value = (boolean) $value; + break; + } + break; + case 'round': + $value = round($value); + break; + case 'multiply': + $value = $value * $data_transformer['transform_by']; + break; + case 'divide': + $value = $value / $data_transformer['transform_by']; + break; + case 'field-contains': + $value = strpos($value, $data_transformer['field_contains_value']) !== false; + break; + case 'field-does-not-contain': + $value = strpos($value, $data_transformer['field_contains_value']) === false; + break; + case 'map-value': + foreach($data_transformer['map_values_ids'] as $map_value) { + if($map_value['old_value'] != $value) { + continue; + } + + $value = $map_value['new_value']; + break; + } + + break; + } + + return $value; + } +} diff --git a/packages/core/classes/import/DataTransformerMapValue.class.php b/packages/core/classes/import/DataTransformerMapValue.class.php new file mode 100644 index 000000000..05c598f86 --- /dev/null +++ b/packages/core/classes/import/DataTransformerMapValue.class.php @@ -0,0 +1,41 @@ + + Some Rights Reserved, Cedric Francoys, 2010-2021 + Licensed under GNU GPL 3 license +*/ +namespace core\import; + +use equal\orm\Model; + +class DataTransformerMapValue extends Model { + + public static function getDescription(): string { + return 'Transform step to adapt external data to eQual entity.'; + } + + public static function getColumns(): array { + return [ + + 'data_transformer_id' => [ + 'type' => 'many2one', + 'foreign_object' => 'core\import\DataTransformer', + 'description' => 'The data transformer this map value is part of.', + 'required' => true + ], + + 'old_value' => [ + 'type' => 'string', + 'description' => 'Old value that needs to be replaced.', + 'required' => true + ], + + 'new_value' => [ + 'type' => 'string', + 'description' => 'New value that needs to be replace the old value.', + 'required' => true + ] + + ]; + } +} diff --git a/packages/core/classes/import/EntityMapping.class.php b/packages/core/classes/import/EntityMapping.class.php new file mode 100644 index 000000000..56d8d76ce --- /dev/null +++ b/packages/core/classes/import/EntityMapping.class.php @@ -0,0 +1,52 @@ + + Some Rights Reserved, Cedric Francoys, 2010-2021 + Licensed under GNU GPL 3 license +*/ +namespace core\import; + +use equal\orm\Model; + +class EntityMapping extends Model { + + public static function getDescription(): string { + return 'Mapping configuration that allows to import a specific entity.'; + } + + public static function getColumns(): array { + return [ + + 'name' => [ + 'type' => 'string', + 'description' => 'Import configuration mapping\'s name.', + 'required' => true + ], + + 'entity' => [ + 'type' => 'string', + 'description' => 'Namespace of the concerned entity.', + 'required' => true + ], + + 'mapping_type' => [ + 'type' => 'string', + 'selection' => [ + 'index' => 'Index', + 'name' => 'Name' + ], + 'description' => 'Are the columns data mapped by index or by name.', + 'default' => 'index', + 'dependents' => ['column_mappings_ids' => ['mapping_type']] + ], + + 'column_mappings_ids' => [ + 'type' => 'one2many', + 'foreign_object' => 'core\import\ColumnMapping', + 'foreign_field' => 'entity_mapping_id', + 'description' => 'Mapping configurations defining how to import data to eQual entity.' + ] + + ]; + } +} diff --git a/packages/core/tests/adapters.php b/packages/core/tests/adapters.php index 3b084213e..6ccc8c11b 100644 --- a/packages/core/tests/adapters.php +++ b/packages/core/tests/adapters.php @@ -472,7 +472,7 @@ return $result; }, 'assert' => function($result) { - return ($result === '10:00'); + return ($result === '10:00:00'); } ], '4108' => [ diff --git a/packages/core/views/import/ColumnMapping.form.default.json b/packages/core/views/import/ColumnMapping.form.default.json new file mode 100644 index 000000000..31d987af0 --- /dev/null +++ b/packages/core/views/import/ColumnMapping.form.default.json @@ -0,0 +1,88 @@ +{ + "name": "Column Mapping", + "description": "Default form for Column Mapping.", + "layout": { + "groups": [ + { + "sections": [ + { + "rows": [ + { + "columns": [ + { + "width": "100%", + "items": [ + { + "type": "field", + "value": "entity_mapping_id", + "width": "25%" + }, + { + "type": "field", + "value": "mapping_type", + "visible": false + } + ] + } + ] + }, + { + "columns": [ + { + "width": "100%", + "items": [ + { + "type": "field", + "value": "origin_name", + "width": "25%" + }, + { + "type": "field", + "value": "origin_index", + "width": "25%" + }, + { + "type": "field", + "value": "", + "width": "25%" + }, + { + "type": "field", + "value": "target_name", + "width": "25%" + } + ] + } + ] + } + ] + } + ] + }, + { + "sections": [ + { + "id": "section.data-transformers", + "label": "Data transformers", + "rows": [ + { + "columns": [ + { + "width": "100%", + "items": [ + { + "type": "field", + "value": "data_transformers_ids", + "width": "100%" + } + ] + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/packages/core/views/import/ColumnMapping.list.default.json b/packages/core/views/import/ColumnMapping.list.default.json new file mode 100644 index 000000000..9c4f4f2cf --- /dev/null +++ b/packages/core/views/import/ColumnMapping.list.default.json @@ -0,0 +1,39 @@ +{ + "name": "Column Mappings", + "description": "Listing view for Column Mapping items.", + "layout": { + "items": [ + { + "type": "field", + "value": "id", + "width": "10%", + "sortable": true, + "readonly": true + }, + { + "type": "field", + "value": "entity_mapping_id", + "width": "20%", + "sortable": true + }, + { + "type": "field", + "value": "mapping_type", + "width": "20%", + "sortable": true + }, + { + "type": "field", + "value": "origin", + "width": "20%", + "sortable": true + }, + { + "type": "field", + "value": "target_name", + "width": "20%", + "sortable": true + } + ] + } +} diff --git a/packages/core/views/import/DataTransformer.form.default.json b/packages/core/views/import/DataTransformer.form.default.json new file mode 100644 index 000000000..2f81ea03e --- /dev/null +++ b/packages/core/views/import/DataTransformer.form.default.json @@ -0,0 +1,99 @@ +{ + "name": "Data Transformer", + "description": "Default form for Data Transformer.", + "layout": { + "groups": [ + { + "sections": [ + { + "rows": [ + { + "columns": [ + { + "width": "100%", + "items": [ + { + "type": "field", + "value": "column_mapping_id", + "width": "25%" + }, + { + "type": "field", + "value": "transformer_type", + "width": "25%" + }, + { + "type": "field", + "value": "", + "width": "25%" + }, + { + "type": "field", + "value": "order", + "width": "25%" + } + ] + } + ] + }, + { + "columns": [ + { + "width": "100%", + "items": [ + { + "type": "field", + "value": "value", + "width": "25%" + }, + { + "type": "field", + "value": "cast_to", + "width": "25%" + }, + { + "type": "field", + "value": "transform_by", + "width": "25%" + }, + { + "type": "field", + "value": "field_contains_value", + "width": "25%" + } + ] + } + ] + } + ] + } + ] + }, + { + "visible": ["transformer_type", "=", "map-value"], + "sections": [ + { + "id": "section.map-values", + "label": "Map values", + "rows": [ + { + "columns": [ + { + "width": "100%", + "items": [ + { + "type": "field", + "value": "map_values_ids", + "width": "100%" + } + ] + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/packages/core/views/import/DataTransformer.list.default.json b/packages/core/views/import/DataTransformer.list.default.json new file mode 100644 index 000000000..696bc64b7 --- /dev/null +++ b/packages/core/views/import/DataTransformer.list.default.json @@ -0,0 +1,33 @@ +{ + "name": "Data Transformers", + "description": "Listing view for Data Transformer items.", + "layout": { + "items": [ + { + "type": "field", + "value": "id", + "width": "10%", + "sortable": true, + "readonly": true + }, + { + "type": "field", + "value": "column_mapping_id", + "width": "40%", + "sortable": true + }, + { + "type": "field", + "value": "transformer_type", + "width": "40%", + "sortable": true + }, + { + "type": "field", + "value": "order", + "width": "10%", + "sortable": true + } + ] + } +} diff --git a/packages/core/views/import/DataTransformerMapValue.form.default.json b/packages/core/views/import/DataTransformerMapValue.form.default.json new file mode 100644 index 000000000..4df21a037 --- /dev/null +++ b/packages/core/views/import/DataTransformerMapValue.form.default.json @@ -0,0 +1,49 @@ +{ + "name": "Data Transformer", + "description": "Default form for Data Transformer.", + "layout": { + "groups": [ + { + "sections": [ + { + "rows": [ + { + "columns": [ + { + "width": "100%", + "items": [ + { + "type": "field", + "value": "data_transformer_id", + "width": "33%" + } + ] + } + ] + }, + { + "columns": [ + { + "width": "100%", + "items": [ + { + "type": "field", + "value": "old_value", + "width": "25%" + }, + { + "type": "field", + "value": "new_value", + "width": "25%" + } + ] + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/packages/core/views/import/DataTransformerMapValue.list.default.json b/packages/core/views/import/DataTransformerMapValue.list.default.json new file mode 100644 index 000000000..9d282a500 --- /dev/null +++ b/packages/core/views/import/DataTransformerMapValue.list.default.json @@ -0,0 +1,33 @@ +{ + "name": "Data Transformer Map Value", + "description": "Listing view for Data Transformer Map Value items.", + "layout": { + "items": [ + { + "type": "field", + "value": "id", + "width": "10%", + "sortable": true, + "readonly": true + }, + { + "type": "field", + "value": "data_transformer_id", + "width": "25%", + "sortable": true + }, + { + "type": "field", + "value": "old_value", + "width": "25%", + "sortable": true + }, + { + "type": "field", + "value": "new_value", + "width": "25%", + "sortable": true + } + ] + } +} diff --git a/packages/core/views/import/EntityMapping.form.default.json b/packages/core/views/import/EntityMapping.form.default.json new file mode 100644 index 000000000..72a28826c --- /dev/null +++ b/packages/core/views/import/EntityMapping.form.default.json @@ -0,0 +1,69 @@ +{ + "name": "Entity Mapping", + "description": "Default form for Entity Mapping.", + "layout": { + "groups": [ + { + "sections": [ + { + "rows": [ + { + "columns": [ + { + "width": "100%", + "items": [ + { + "type": "field", + "value": "name", + "width": "33%" + }, + { + "type": "field", + "value": "", + "width": "33%" + }, + { + "type": "field", + "value": "entity", + "width": "33%" + }, + { + "type": "field", + "value": "mapping_type", + "width": "25%" + } + ] + } + ] + } + ] + } + ] + }, + { + "sections": [ + { + "id": "section.column-mappings", + "label": "Column mappings", + "rows": [ + { + "columns": [ + { + "width": "100%", + "items": [ + { + "type": "field", + "value": "column_mappings_ids", + "width": "100%" + } + ] + } + ] + } + ] + } + ] + } + ] + } +} diff --git a/packages/core/views/import/EntityMapping.list.default.json b/packages/core/views/import/EntityMapping.list.default.json new file mode 100644 index 000000000..3f4fedf11 --- /dev/null +++ b/packages/core/views/import/EntityMapping.list.default.json @@ -0,0 +1,36 @@ +{ + "name": "Entity Mappings", + "description": "Listing view for Entity Mapping items.", + "layout": { + "items": [ + { + "type": "field", + "value": "id", + "width": "10%", + "sortable": true, + "readonly": true + }, + { + "type": "field", + "value": "name", + "width": "33%", + "sortable": true, + "readonly": false + }, + { + "type": "field", + "value": "entity", + "width": "33%", + "sortable": true, + "readonly": false + }, + { + "type": "field", + "value": "mapping_type", + "width": "25%", + "sortable": true, + "readonly": false + } + ] + } +} diff --git a/packages/core/views/menu.settings.left.json b/packages/core/views/menu.settings.left.json index 1afc748b6..9cc19b7f1 100644 --- a/packages/core/views/menu.settings.left.json +++ b/packages/core/views/menu.settings.left.json @@ -164,6 +164,26 @@ } ] }, + { + "id": "settings.import", + "label": "Import", + "description": "", + "icon": "settings_applications", + "type": "parent", + "children": [ + { + "id": "settings.import.entity-mappings", + "label": "Entity mappings", + "description": "", + "icon": "device_hub", + "type": "entry", + "context": { + "entity": "core\\import\\EntityMapping", + "view": "list.default" + } + } + ] + }, { "id": "settings.settings", "label": "Settings", diff --git a/public/assets/img/equal_summary.png b/public/assets/img/equal_summary.png index 385b06f68..a52e1a869 100644 Binary files a/public/assets/img/equal_summary.png and b/public/assets/img/equal_summary.png differ diff --git a/public/console.php b/public/console.php index 95bf3a362..88b45b85f 100644 --- a/public/console.php +++ b/public/console.php @@ -1,13 +1,15 @@ - Some Rights Reserved, Cedric Francoys, 2010-2023 - Licensed under GNU LGPL 3 license + Some Rights Reserved, The eQual Framework, 2010-2024 + Author: The eQual Framework Contributors + Original Author: Cedric Francoys + License: GNU LGPL 3 license */ error_reporting(0); // get log file, using variation from URL, if any -$log_file = (isset($_GET['f']) && strlen($_GET['f']))?$_GET['f']:'equal.log'; +$log_file = (isset($_GET['f']) && strlen($_GET['f'])) ? $_GET['f'] : 'equal.log'; // retrieve logs history (variations on filename) $log_variations = []; @@ -310,7 +312,7 @@ bottom: 20px; opacity: 1; } - div.no-result { + div.feedback { margin-left: 20px; } div.no-result::before { @@ -495,6 +497,11 @@ classname = "text"; query = "a=1"; } const response = await fetch("console.php?"+query); + + if(response.status != 200) { + throw new Error(response.status); + } + const data = await response.json(); return data; } @@ -629,14 +636,20 @@ function createTraceElement(trace, i) { list.style.display = "none"; list.innerHTML = ""; document.getElementById("loader").style.display = "block"; - const threads = await get_threads(params); + try { + const threads = await get_threads(params); - for(const thread of threads) { - let element = createThreadElement(thread, params); - list.prepend(element); + for(const thread of threads) { + let element = createThreadElement(thread, params); + list.prepend(element); + } + if(!threads.length) { + list.innerHTML = "
"; + } } - if(!threads.length) { - list.innerHTML = "
"; + catch(status) { + console.log("an error occurred", status); + list.innerHTML = "
Filesize over limit. Parsing blocked to prevent overload.
"; } document.getElementById("loader").style.display = "none"; list.style.display = "block"; @@ -810,6 +823,15 @@ function createTraceElement(trace, i) { unset($_GET['date']); } + $filesize = filesize('../log/'.$log_file); + $MAX_FILESIZE = 100 * 1000 * 1000; + // limit processing base on filesize to prevent overload + if($filesize > $MAX_FILESIZE) { + // set response as 'no content' + http_response_code(204); + die(); + } + // read raw data from log file if($f = fopen('../log/'.$log_file, 'r')) { diff --git a/public/index.php b/public/index.php index 36174aa06..39b771818 100644 --- a/public/index.php +++ b/public/index.php @@ -1,8 +1,9 @@ - Some Rights Reserved, Cedric Francoys, 2010-2023 - Licensed under GNU LGPL 3 license + Some Rights Reserved, The eQual Framework, 2010-2024 + Original Author: Cedric Francoys + License: GNU LGPL 3 license */ /* diff --git a/run.php b/run.php index cab99b372..3ae3110cf 100644 --- a/run.php +++ b/run.php @@ -3,8 +3,9 @@ * This file is part of the eQual framework. * https://github.com/equalframework/equal * -* Some Rights Reserved, Cedric Francoys, 2010-2024 -* Licensed under GNU LGPL 3 license +* Some Rights Reserved, The eQual Framework, 2010-2024 +* Original Author: Cedric Francoys +* License: GNU LGPL 3 license * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by