PackageManager.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. <?php
  2. /*
  3. * This file is part of Packagist.
  4. *
  5. * (c) Jordi Boggiano <j.boggiano@seld.be>
  6. * Nils Adermann <naderman@naderman.de>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Packagist\WebBundle\Model;
  12. use Symfony\Bridge\Doctrine\RegistryInterface;
  13. use Packagist\WebBundle\Entity\Package;
  14. use Psr\Log\LoggerInterface;
  15. use Algolia\AlgoliaSearch\SearchClient;
  16. use Predis\Client;
  17. use Packagist\WebBundle\Service\GitHubUserMigrationWorker;
  18. /**
  19. * @author Jordi Boggiano <j.boggiano@seld.be>
  20. */
  21. class PackageManager
  22. {
  23. protected $doctrine;
  24. protected $mailer;
  25. protected $instantMailer;
  26. protected $twig;
  27. protected $logger;
  28. protected $options;
  29. protected $providerManager;
  30. protected $algoliaClient;
  31. protected $algoliaIndexName;
  32. protected $githubWorker;
  33. protected $metadataDir;
  34. public function __construct(RegistryInterface $doctrine, \Swift_Mailer $mailer, \Swift_Mailer $instantMailer, \Twig_Environment $twig, LoggerInterface $logger, array $options, ProviderManager $providerManager, SearchClient $algoliaClient, string $algoliaIndexName, GitHubUserMigrationWorker $githubWorker, string $metadataDir, Client $redis)
  35. {
  36. $this->doctrine = $doctrine;
  37. $this->mailer = $mailer;
  38. $this->instantMailer = $instantMailer;
  39. $this->twig = $twig;
  40. $this->logger = $logger;
  41. $this->options = $options;
  42. $this->providerManager = $providerManager;
  43. $this->algoliaClient = $algoliaClient;
  44. $this->algoliaIndexName = $algoliaIndexName;
  45. $this->githubWorker = $githubWorker;
  46. $this->metadataDir = $metadataDir;
  47. $this->redis = $redis;
  48. }
  49. public function deletePackage(Package $package)
  50. {
  51. /** @var VersionRepository $versionRepo */
  52. $versionRepo = $this->doctrine->getRepository('PackagistWebBundle:Version');
  53. foreach ($package->getVersions() as $version) {
  54. $versionRepo->remove($version);
  55. }
  56. if ($package->getAutoUpdated() === Package::AUTO_GITHUB_HOOK) {
  57. foreach ($package->getMaintainers() as $maintainer) {
  58. $token = $maintainer->getGithubToken();
  59. try {
  60. if ($token && $this->githubWorker->deleteWebHook($token, $package)) {
  61. break;
  62. }
  63. } catch (\GuzzleHttp\Exception\TransferException $e) {
  64. // ignore
  65. }
  66. }
  67. }
  68. $em = $this->doctrine->getManager();
  69. $downloadRepo = $this->doctrine->getRepository('PackagistWebBundle:Download');
  70. $downloadRepo->deletePackageDownloads($package);
  71. $emptyRefRepo = $this->doctrine->getRepository('PackagistWebBundle:EmptyReferenceCache');
  72. $emptyRef = $emptyRefRepo->findOneBy(['package' => $package]);
  73. if ($emptyRef) {
  74. $em->remove($emptyRef);
  75. $em->flush();
  76. }
  77. $this->providerManager->deletePackage($package);
  78. $packageId = $package->getId();
  79. $packageName = $package->getName();
  80. $em->remove($package);
  81. $em->flush();
  82. $metadataV2 = $this->metadataDir.'/p2/'.strtolower($packageName).'.json';
  83. if (file_exists($metadataV2)) {
  84. @unlink($metadataV2);
  85. }
  86. if (file_exists($metadataV2.'.gz')) {
  87. @unlink($metadataV2.'.gz');
  88. }
  89. $metadataV2Dev = $this->metadataDir.'/p2/'.strtolower($packageName).'~dev.json';
  90. if (file_exists($metadataV2Dev)) {
  91. @unlink($metadataV2Dev);
  92. }
  93. if (file_exists($metadataV2Dev.'.gz')) {
  94. @unlink($metadataV2Dev.'.gz');
  95. }
  96. // delete redis stats
  97. try {
  98. $this->redis->del('views:'.$packageId);
  99. } catch (\Predis\Connection\ConnectionException $e) {
  100. }
  101. $this->redis->zadd('metadata-deletes', round(microtime(true)*10000), strtolower($packageName));
  102. // attempt search index cleanup
  103. try {
  104. $indexName = $this->algoliaIndexName;
  105. $algolia = $this->algoliaClient;
  106. $index = $algolia->initIndex($indexName);
  107. $index->deleteObject($packageName);
  108. } catch (\AlgoliaSearch\AlgoliaException $e) {
  109. }
  110. }
  111. public function notifyUpdateFailure(Package $package, \Exception $e, $details = null)
  112. {
  113. if (!$package->isUpdateFailureNotified()) {
  114. $recipients = array();
  115. foreach ($package->getMaintainers() as $maintainer) {
  116. if ($maintainer->isNotifiableForFailures()) {
  117. $recipients[$maintainer->getEmail()] = $maintainer->getUsername();
  118. }
  119. }
  120. if ($recipients) {
  121. $body = $this->twig->render('PackagistWebBundle:email:update_failed.txt.twig', array(
  122. 'package' => $package,
  123. 'exception' => get_class($e),
  124. 'exceptionMessage' => $e->getMessage(),
  125. 'details' => strip_tags($details),
  126. ));
  127. $message = (new \Swift_Message)
  128. ->setSubject($package->getName().' failed to update, invalid composer.json data')
  129. ->setFrom($this->options['from'], $this->options['fromName'])
  130. ->setTo($recipients)
  131. ->setBody($body)
  132. ;
  133. try {
  134. $this->instantMailer->send($message);
  135. } catch (\Swift_TransportException $e) {
  136. $this->logger->error('['.get_class($e).'] '.$e->getMessage());
  137. return false;
  138. }
  139. }
  140. $package->setUpdateFailureNotified(true);
  141. }
  142. // make sure the package crawl time is updated so we avoid retrying failing packages more often than working ones
  143. if (!$package->getCrawledAt() || $package->getCrawledAt() < new \DateTime()) {
  144. $package->setCrawledAt(new \DateTime);
  145. }
  146. $this->doctrine->getEntityManager()->flush();
  147. return true;
  148. }
  149. public function notifyNewMaintainer($user, $package)
  150. {
  151. $body = $this->twig->render('PackagistWebBundle:email:maintainer_added.txt.twig', array(
  152. 'package_name' => $package->getName()
  153. ));
  154. $message = (new \Swift_Message)
  155. ->setSubject('You have been added to ' . $package->getName() . ' as a maintainer')
  156. ->setFrom($this->options['from'], $this->options['fromName'])
  157. ->setTo($user->getEmail())
  158. ->setBody($body)
  159. ;
  160. try {
  161. $this->mailer->send($message);
  162. } catch (\Swift_TransportException $e) {
  163. $this->logger->error('['.get_class($e).'] '.$e->getMessage());
  164. return false;
  165. }
  166. return true;
  167. }
  168. }