src/StartPlatz/Bundle/MentorsBundle/Controller/PublicController.php line 436

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace App\StartPlatz\Bundle\MentorsBundle\Controller;
  3. use App\StartPlatz\Bundle\MentorsBundle\Service\MentorService;
  4. use App\StartPlatz\Bundle\MentorsBundle\Service\WordPressMenuService;
  5. use Doctrine\ORM\EntityManagerInterface;
  6. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  7. use Symfony\Component\HttpFoundation\Request;
  8. use Symfony\Component\HttpFoundation\Response;
  9. use Symfony\Component\Routing\Annotation\Route;
  10. /**
  11.  * PublicController
  12.  *
  13.  * Handles public-facing mentor and expert pages
  14.  */
  15. class PublicController extends AbstractController
  16. {
  17.     private MentorService $mentorService;
  18.     private WordPressMenuService $menuService;
  19.     private EntityManagerInterface $entityManager;
  20.     public function __construct(
  21.         MentorService $mentorService,
  22.         WordPressMenuService $menuService,
  23.         EntityManagerInterface $entityManager
  24.     ) {
  25.         $this->mentorService $mentorService;
  26.         $this->menuService $menuService;
  27.         $this->entityManager $entityManager;
  28.     }
  29.     /**
  30.      * STARTPLATZ Mentors page
  31.      */
  32.     #[Route('/mentors/startplatz'name'mentors_startplatz')]
  33.     #[Route('/en/mentors/startplatz'name'mentors_startplatz_en')]
  34.     public function startplatzMentorsAction(Request $request): Response
  35.     {
  36.         $locale $request->getLocale();
  37.         $isEnglish str_contains($request->getPathInfo(), '/en/');
  38.         // Get all mentors (no pagination for section-based display)
  39.         $allMentors $this->mentorService->getMentorsByCategory(
  40.             MentorService::CATEGORY_STARTPLATZ,
  41.             []
  42.         );
  43.         // Define categories in the order they should appear (matching original page)
  44.         $categoryOrder = [
  45.             'apps-it' => 'Apps und IT',
  46.             'marketing' => 'Marketing',
  47.             'finanzierung' => 'Finanzierung',
  48.             'gruendung-fuehrung' => 'Gründung und Führung',
  49.             'recht' => 'Recht',
  50.             'design' => 'Design',
  51.             'wirtschaftspruefung-steuerberatung' => 'Wirtschaftsprüfung und Steuerberatung',
  52.             'versicherung' => 'Versicherung'
  53.         ];
  54.         // Group mentors by category
  55.         $mentorsByCategory = [];
  56.         // Initialize all categories even if empty
  57.         foreach ($categoryOrder as $categoryKey => $categoryLabel) {
  58.             $mentorsByCategory[$categoryKey] = [
  59.                 'label' => $categoryLabel,
  60.                 'mentors' => []
  61.             ];
  62.         }
  63.         // Process each mentor
  64.         foreach ($allMentors as $mentor) {
  65.             $metadata $mentor->getMentorMetadata() ?? [];
  66.             // Get mentor's categories from mentorMetadata ONLY
  67.             // If no categories defined, assign to default category
  68.             $mentorCategories $metadata['categories'] ?? ['gruendung-fuehrung'];
  69.             if (!is_array($mentorCategories)) {
  70.                 $mentorCategories = [$mentorCategories];
  71.             }
  72.             // Prepare mentor data - use existing member fields
  73.             $mentorData = [
  74.                 'id' => $mentor->getId(),
  75.                 'name' => trim($mentor->getFirstName() . ' ' $mentor->getLastName()),
  76.                 'firstName' => $mentor->getFirstName(),
  77.                 'lastName' => $mentor->getLastName(),
  78.                 'email' => $mentor->getEmail(),
  79.                 'title' => $mentor->getJobTitle() ?? '',
  80.                 'academicTitle' => $mentor->getTitle() ?? '',
  81.                 'company' => $mentor->getMentorFirma() ?? $mentor->getCompany() ?? '',
  82.                 'description' => $mentor->getMentorProfile() ?? $mentor->getDescription() ?? '',
  83.                 'image' => $mentor->getImageLink() ?? 'https://res.cloudinary.com/startplatz/image/upload/v1637360496/Wordpress/Sprechstunden/Sprechstunden_Platzhalter.jpg',
  84.                 'skills' => [], // Skills werden aus mentor_profile extrahiert in der Template
  85.                 'slug' => strtolower(str_replace(' ''-'trim($mentor->getFirstName() . '-' $mentor->getLastName())))
  86.             ];
  87.             // Add to appropriate categories (or to first category if none specified)
  88.             if (empty($mentorCategories)) {
  89.                 // If no category specified, add to first category
  90.                 $firstKey array_key_first($categoryOrder);
  91.                 $mentorsByCategory[$firstKey]['mentors'][] = $mentorData;
  92.             } else {
  93.                 // Add to all specified categories
  94.                 foreach ($mentorCategories as $category) {
  95.                     if (isset($mentorsByCategory[$category])) {
  96.                         $mentorsByCategory[$category]['mentors'][] = $mentorData;
  97.                     }
  98.                 }
  99.             }
  100.         }
  101.         // Remove empty categories if needed (optional)
  102.         // $mentorsByCategory = array_filter($mentorsByCategory, function($category) {
  103.         //     return !empty($category['mentors']);
  104.         // });
  105.         return $this->render('@StartPlatzMentors/Public/startplatz-mentors.html.twig', [
  106.             'mentorsByCategory' => $mentorsByCategory,
  107.             'isEnglish' => $isEnglish
  108.         ]);
  109.     }
  110.     /**
  111.      * Determine categories based on mentor's skills
  112.      * This is a fallback when mentorMetadata doesn't have categories defined
  113.      * Uses the first 3 skills to determine the best matching category
  114.      */
  115.     private function determineCategoriesFromMentor($mentor): array
  116.     {
  117.         // Get mentorMetadata to check for skills
  118.         $metadata $mentor->getMentorMetadata() ?? [];
  119.         $skills $metadata['skills'] ?? [];
  120.         // If no skills in metadata, we can't categorize properly
  121.         // Default to 'gruendung-fuehrung' as the most general category
  122.         if (empty($skills)) {
  123.             return ['gruendung-fuehrung'];
  124.         }
  125.         // Convert skills to lowercase for matching
  126.         $skillsLower array_map('strtolower'array_slice($skills03)); // Use first 3 skills
  127.         $categories = [];
  128.         // Keywords for each category based on skills
  129.         $categorySkillKeywords = [
  130.             'apps-it' => ['software''app''tech''digital''entwicklung''programmier''web''mobile''cyber''it''code''system'],
  131.             'marketing' => ['marketing''brand''pr''social media''content''seo''kommunikation''werbung''sales''vertrieb'],
  132.             'finanzierung' => ['finanz''invest''venture capital''capital''funding''förder''angel''seed''fundraising'],
  133.             'gruendung-fuehrung' => ['gründ''startup''führung''management''strateg''business''unternehm''leadership'],
  134.             'recht' => ['recht''anwalt''jurist''legal''vertrag''gesetz''compliance''datenschutz''gesellschaftsrecht'],
  135.             'design' => ['design''ux''ui''grafik''kreativ''visual''prototype''user experience'],
  136.             'wirtschaftspruefung-steuerberatung' => ['steuer''wirtschaftsprüf''tax''audit''buchführung''bilanz''accounting'],
  137.             'versicherung' => ['versicher''vorsorge''risiko''haftpflicht''kranken''altersvorsorge']
  138.         ];
  139.         // Check each skill against category keywords
  140.         foreach ($skillsLower as $skill) {
  141.             foreach ($categorySkillKeywords as $category => $keywords) {
  142.                 foreach ($keywords as $keyword) {
  143.                     if (str_contains($skill$keyword)) {
  144.                         if (!in_array($category$categories)) {
  145.                             $categories[] = $category;
  146.                         }
  147.                         break; // Found match for this category in this skill
  148.                     }
  149.                 }
  150.             }
  151.         }
  152.         // If no category found from skills, default to 'gruendung-fuehrung'
  153.         if (empty($categories)) {
  154.             $categories[] = 'gruendung-fuehrung';
  155.         }
  156.         // Return only the first matched category to avoid appearing in all categories
  157.         return [array_shift($categories)];
  158.     }
  159.     /**
  160.      * Extract skills from description text
  161.      * This is a fallback when mentorMetadata doesn't have skills defined
  162.      */
  163.     private function extractSkillsFromDescription(?string $description): array
  164.     {
  165.         if (empty($description)) {
  166.             return [];
  167.         }
  168.         // Simple extraction based on common keywords
  169.         $skills = [];
  170.         // Common skill patterns in German mentor descriptions
  171.         $patterns = [
  172.             '/(?:Beratung|Expertise|Schwerpunkt|Fokus|Spezialisierung)(?:\s+(?:in|auf|für))?\s*:?\s*([^.]+)/i',
  173.             '/(?:berate|unterstütze|helfe)(?:\s+bei)?\s*:?\s*([^.]+)/i'
  174.         ];
  175.         foreach ($patterns as $pattern) {
  176.             if (preg_match($pattern$description$matches)) {
  177.                 $skillText $matches[1];
  178.                 // Split by common delimiters
  179.                 $potentialSkills preg_split('/[,;]/'$skillText);
  180.                 foreach ($potentialSkills as $skill) {
  181.                     $skill trim($skill);
  182.                     if (strlen($skill) > && strlen($skill) < 50) {
  183.                         $skills[] = $skill;
  184.                         if (count($skills) >= 3) {
  185.                             return $skills;
  186.                         }
  187.                     }
  188.                 }
  189.             }
  190.         }
  191.         // If no skills found via patterns, use first sentence as description
  192.         return [];
  193.     }
  194.     /**
  195.      * Categorize mentor by their skills
  196.      */
  197.     private function categorizeMentorBySkills(array $skills): array
  198.     {
  199.         // Define category keywords (German and English)
  200.         $categoryKeywords = [
  201.             'tech-development' => [
  202.                 'entwicklung''development''programming''software''code''coding',
  203.                 'python''javascript''java''php''backend''frontend''fullstack',
  204.                 'devops''cloud''aws''docker''kubernetes''api''database''sql',
  205.                 'machine learning''ai''ki''artificial intelligence''deep learning',
  206.                 'neural''tensorflow''pytorch''data science''algorithm''tech''it',
  207.                 'mobile''app''web''security''blockchain''crypto''iot''embedded',
  208.                 'github''gitlab''git''bitbucket''technologie''technology',
  209.                 'react''angular''vue''node''typescript''swift''kotlin',
  210.                 'android''ios''flutter''xamarin''unity''unreal',
  211.                 'azure''gcp''serverless''microservices''ci/cd''jenkins',
  212.                 'mysql''postgresql''mongodb''redis''elasticsearch''ingenieurwesen'
  213.             ],
  214.             'business-strategy' => [
  215.                 'strategy''strategie''business''geschäft''model''skalierung',
  216.                 'scaling''growth''wachstum''management''führung''leadership',
  217.                 'operations''prozesse''transformation''innovation''planung',
  218.                 'roadmap''vision''mission''okr''kpi''agile''scrum''lean',
  219.                 'startup''gründung''entrepreneurship''ceo''coo''executive',
  220.                 'consulting''beratung''change''digital transformation'
  221.             ],
  222.             'sales-marketing' => [
  223.                 'sales''vertrieb''marketing''growth''hacking''b2b''b2c',
  224.                 'seo''sea''sem''social media''content''brand''marke''pr',
  225.                 'communication''kommunikation''crm''lead''conversion''funnel',
  226.                 'customer''acquisition''retention''email''advertising''werbung',
  227.                 'campaign''analytics''performance''influencer''storytelling',
  228.                 'copywriting''demand''generation''outbound''inbound'
  229.             ],
  230.             'finance-legal' => [
  231.                 'finance''finanzen''funding''investment''investor''vc',
  232.                 'venture''capital''förderung''grant''pitch''valuation',
  233.                 'legal''recht''anwalt''lawyer''compliance''dsgvo''gdpr',
  234.                 'vertrag''contract''ip''patent''steuer''tax''accounting',
  235.                 'buchführung''controlling''budget''cashflow''revenue''profit',
  236.                 'seed''series''exit''ipo''due diligence''term sheet'
  237.             ],
  238.             'product-design' => [
  239.                 'product''produkt''design''ux''ui''user experience''usability',
  240.                 'customer''development''mvp''prototype''iteration''lean startup',
  241.                 'feedback''research''testing''interface''interaction',
  242.                 'creative''kreativ''brand''visual''figma''sketch''adobe',
  243.                 'wireframe''mockup''persona''journey''map''sprint',
  244.                 'discovery''validation''feature''roadmap''backlog'
  245.             ]
  246.         ];
  247.         if (empty($skills)) {
  248.             return ['business-strategy']; // Default category
  249.         }
  250.         // Convert skills to lowercase for matching
  251.         $skillsLower array_map('strtolower'$skills);
  252.         $categoryScores = [];
  253.         foreach ($categoryKeywords as $category => $keywords) {
  254.             $score 0;
  255.             $matches = [];
  256.             foreach ($skillsLower as $skill) {
  257.                 // Remove hashtag if present
  258.                 $skill ltrim(trim($skill), '#');
  259.                 // Split hyphenated words for better matching
  260.                 $skillParts preg_split('/[-\s]+/'$skill);
  261.                 foreach ($keywords as $keyword) {
  262.                     // Exact match: 3 points
  263.                     if ($skill === $keyword) {
  264.                         $score += 3;
  265.                         $matches[] = $skill;
  266.                         break; // Don't count same skill multiple times
  267.                     }
  268.                     // Check parts of hyphenated words
  269.                     foreach ($skillParts as $part) {
  270.                         if ($part === $keyword) {
  271.                             $score += 2// Give 2 points for exact part match
  272.                             $matches[] = $skill;
  273.                             break 2// Break both loops
  274.                         }
  275.                     }
  276.                     // Partial match: 1 point - but only for whole word matches
  277.                     // Use word boundaries to avoid false positives like "it" in "exit"
  278.                     if (preg_match('/\b' preg_quote($keyword'/') . '\b/i'$skill)) {
  279.                         $score += 1;
  280.                         $matches[] = $skill;
  281.                         break;
  282.                     }
  283.                 }
  284.             }
  285.             // Only consider categories with score >= 2
  286.             if ($score >= 2) {
  287.                 $categoryScores[$category] = [
  288.                     'score' => $score,
  289.                     'matches' => array_unique($matches)
  290.                 ];
  291.             }
  292.         }
  293.         // If no categories matched, return default
  294.         if (empty($categoryScores)) {
  295.             return ['business-strategy'];
  296.         }
  297.         // Sort by score and return top 3 categories
  298.         arsort($categoryScores);
  299.         return array_slice(array_keys($categoryScores), 03);
  300.     }
  301.     /**
  302.      * AI Accelerator Mentors page
  303.      */
  304.     #[Route('/accelerator/mentors'name'accelerator_mentors')]
  305.     #[Route('/en/accelerator/mentors'name'accelerator_mentors_en')]
  306.     public function acceleratorMentorsAction(Request $request): Response
  307.     {
  308.         $isEnglish str_contains($request->getPathInfo(), '/en/');
  309.         // Get filters
  310.         $filters = [
  311.             'search' => $request->get('search'),
  312.             'expertise' => $request->get('expertise'),
  313.             'techStack' => $request->get('tech')
  314.         ];
  315.         // Get mentors
  316.         $allMentors $this->mentorService->getMentorsByCategory(
  317.             MentorService::CATEGORY_ACCELERATOR,
  318.             array_filter($filters)
  319.         );
  320.         // Prepare mentor data arrays (similar to STARTPLATZ mentors)
  321.         $mentorsData = [];
  322.         foreach ($allMentors as $mentor) {
  323.             $metadata $mentor->getMentorMetadata() ?? [];
  324.             // Prepare skills array - use the actual skills field from member
  325.             $skills = [];
  326.             $skillsString $mentor->getSkills();
  327.             if (!empty($skillsString)) {
  328.                 // Split by spaces or commas, filter empty values
  329.                 $skills preg_split('/[\s,]+/'$skillsString);
  330.                 $skills array_filter($skills, function($s) { return !empty(trim($s)); });
  331.             }
  332.             // Categorize mentor based on skills
  333.             $categories $this->categorizeMentorBySkills($skills);
  334.             // Prepare mentor data - use existing member fields with metadata fallback
  335.             $mentorData = [
  336.                 'id' => $mentor->getId(),
  337.                 'name' => trim($mentor->getFirstName() . ' ' $mentor->getLastName()),
  338.                 'firstName' => $mentor->getFirstName(),
  339.                 'lastName' => $mentor->getLastName(),
  340.                 'email' => $mentor->getEmail(),
  341.                 'title' => $mentor->getJobTitle() ?? $metadata['title'] ?? '',
  342.                 'academicTitle' => $mentor->getTitle() ?? '',
  343.                 'company' => $mentor->getCompany() ?? $metadata['company'] ?? '',
  344.                 'mentorFirma' => $mentor->getMentorFirma(), // Added for institution fallback
  345.                 'description' => $mentor->getDescription() ?? $metadata['description'] ?? '',
  346.                 'profileImage' => $mentor->getImageLink() ?? $metadata['profileImage'] ?? null,
  347.                 'image' => $mentor->getImageLink() ?? $metadata['profileImage'] ?? null,
  348.                 'skills' => $skills,
  349.                 'categories' => $categories// Add categories for filtering
  350.                 'expertise' => $metadata['expertise'] ?? null,
  351.                 'linkedin' => $metadata['linkedin'] ?? null,
  352.                 'mentorProfile' => $mentor->getMentorProfile(),
  353.                 'jobTitle' => $mentor->getJobTitle(),
  354.                 'mentorMetadata' => $metadata// Include full metadata for new fields
  355.                 'github' => $metadata['github'] ?? null,
  356.                 'twitter' => $metadata['twitter'] ?? null
  357.             ];
  358.             $mentorsData[] = $mentorData;
  359.         }
  360.         // Get menu from WordPress
  361.         $menu $this->menuService->getAcceleratorMenu($isEnglish$request->getPathInfo());
  362.         return $this->render('@StartPlatzMentors/Public/accelerator-mentors.html.twig', [
  363.             'mentors' => $mentorsData,
  364.             'searchQuery' => $filters['search'] ?? '',
  365.             'currentExpertise' => $filters['expertise'] ?? null,
  366.             'currentTechStack' => $filters['techStack'] ?? null,
  367.             'isEnglish' => $isEnglish,
  368.             'pageTitle' => $isEnglish 'AI Accelerator Mentors' 'KI Accelerator Mentoren',
  369.             'pageDescription' => $isEnglish
  370.                 'Expert mentors for AI startups'
  371.                 'Experten-Mentoren für KI-Startups',
  372.             'menu' => $menu
  373.         ]);
  374.     }
  375.     /**
  376.      * Experts in Residence page
  377.      */
  378.     #[Route('/accelerator/experts-in-residence'name'experts_residence')]
  379.     #[Route('/en/accelerator/experts-in-residence'name'experts_residence_en')]
  380.     public function expertsResidenceAction(Request $request): Response
  381.     {
  382.         $isEnglish str_contains($request->getPathInfo(), '/en/');
  383.         // Get filters
  384.         $filters = [
  385.             'search' => $request->get('search'),
  386.             'specialization' => $request->get('specialization'),
  387.             'limit' => 20,
  388.             'offset' => ($request->get('page'1) - 1) * 20
  389.         ];
  390.         // Get experts
  391.         $allExperts $this->mentorService->getMentorsByCategory(
  392.             MentorService::CATEGORY_EXPERTS,
  393.             array_filter($filters)
  394.         );
  395.         // Prepare expert data arrays (similar to STARTPLATZ mentors)
  396.         $expertsData = [];
  397.         foreach ($allExperts as $expert) {
  398.             // Get full metadata and expert-specific metadata
  399.             $fullMetadata $expert->getMentorMetadata() ?? [];
  400.             $expertMetadata $expert->getExpertMetadata(); // Uses the new expert namespace
  401.             // Prepare expert data - use Member entity directly with enriched metadata
  402.             $expertData = [
  403.                 'id' => $expert->getId(),
  404.                 'name' => trim($expert->getFirstName() . ' ' $expert->getLastName()),
  405.                 'firstName' => $expert->getFirstName(),
  406.                 'lastName' => $expert->getLastName(),
  407.                 'email' => $expert->getEmail(),
  408.                 'profileImage' => $expert->getImageLink(),
  409.                 'whoAmI' => $expert->getDescription() ?: $expert->getMentorProfile(), // Bio/description field with fallback
  410.                 'skills' => $expert->getSkills(),
  411.                 'linkedin' => $expert->getLinkedin(),
  412.                 'twitter' => $expert->getTwitter(),
  413.                 // Include the expert metadata for the template
  414.                 'expertMetadata' => $expertMetadata,
  415.                 // Legacy fields for backward compatibility
  416.                 'title' => $expert->getJobTitle() ?? $expertMetadata['title'] ?? '',
  417.                 'academicTitle' => $expert->getTitle() ?? '',
  418.                 'company' => $expert->getCompany() ?? $expertMetadata['companyAffiliation'] ?? '',
  419.                 'description' => $expert->getDescription(),
  420.                 'image' => $expert->getImageLink(),
  421.                 'specializations' => $expertMetadata['expertiseAreas'] ?? [],
  422.                 'currentProject' => $fullMetadata['currentProject'] ?? null,
  423.                 'website' => $fullMetadata['website'] ?? null
  424.             ];
  425.             $expertsData[] = $expertData;
  426.         }
  427.         // Determine language for template
  428.         $language $isEnglish 'en' 'de';
  429.         // Get menu from WordPress
  430.         $menu $this->menuService->getAcceleratorMenu($isEnglish$request->getPathInfo());
  431.         return $this->render('@StartPlatzMentors/Public/experts-residence.html.twig', [
  432.             'experts' => $expertsData,
  433.             'searchQuery' => $filters['search'] ?? '',
  434.             'currentSpecialization' => $filters['specialization'] ?? null,
  435.             'isEnglish' => $isEnglish,
  436.             'language' => $language,
  437.             'pageTitle' => 'Expert-in-Residence Program',
  438.             'pageDescription' => $isEnglish
  439.                 'Making local excellence in Artificial Intelligence visible and impactful'
  440.                 'Lokale Exzellenz in der Künstlichen Intelligenz sichtbar und wirksam machen',
  441.             'menu' => $menu
  442.         ]);
  443.     }
  444. }